Reading about std::unique_ptr
at http://en.cppreference.com/w/cpp/memory/unique_ptr, my naive impression is that a smart enough compiler could replace correct uses of unique_ptr
with bare pointers and just put in a delete
when the unique_ptr
s get destroyed. Is this actually the case? If so, do any of the mainstream optimizing compilers actually do this? If not, would it be possible to write something with some/all of unique_ptr
s compile-time safety benefits that could be optimized to have no runtime cost (in space or time)?
Note to those (properly) worried about premature optimization: The answer here won't stop me from using std::unique_ptr
, I'm just curious if it's a really awesome tool or just an awesome one.
EDIT 2013/07/21 20:07 EST:
OK, so I tested with the following program (please let me know if there's something wrong with this):
#include <climits>
#include <chrono>
#include <memory>
#include <iostream>
static const size_t iterations = 100;
int main (int argc, char ** argv) {
std::chrono::steady_clock::rep smart[iterations];
std::chrono::steady_clock::rep dumb[iterations];
volatile int contents;
for (size_t i = 0; i < iterations; i++) {
auto start = std::chrono::steady_clock::now();
{
std::unique_ptr<int> smart_ptr(new int(5));
for (unsigned int j = 0; j < UINT_MAX; j++)
contents = *smart_ptr;
}
auto middle = std::chrono::steady_clock::now();
{
int *dumb_ptr = new int(10);
try {
for (unsigned int j = 0; j < UINT_MAX; j++)
contents = *dumb_ptr;
delete dumb_ptr;
} catch (...) {
delete dumb_ptr;
throw;
}
}
auto end = std::chrono::steady_clock::now();
smart[i] = (middle - start).count();
dumb[i] = (end - middle).count();
}
std::chrono::steady_clock::rep smartAvg;
std::chrono::steady_clock::rep dumbAvg;
for (size_t i = 0; i < iterations; i++) {
smartAvg += smart[i];
dumbAvg += dumb[i];
}
smartAvg /= iterations;
dumbAvg /= iterations;
std::cerr << "Smart: " << smartAvg << " Dumb: " << dumbAvg << std::endl;
return contents;
}
Compiling with g++ 4.7.3 using g++ --std=c++11 -O3 test.cc
gave Smart: 1130859 Dumb: 1130005
, which means the smart pointer is within 0.076% of the dumb pointer, which is almost surely noise.
unique_ptr. An unique_ptr has exclusive ownership of the object it points to and will destroy the object when the pointer goes out of scope.
An explicit delete for a unique_ptr would be reset() . But do remember that unique_ptr are there so that you don't have to manage directly the memory they hold. That is, you should know that a unique_ptr will safely delete its underlying raw pointer once it goes out of scope.
unique_ptr objects automatically delete the object they manage (using a deleter) as soon as they themselves are destroyed, or as soon as their value changes either by an assignment operation or by an explicit call to unique_ptr::reset.
Use unique_ptr when you want to have single ownership(Exclusive) of the resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another. A shared_ptr is a container for raw pointers.
It would certainly be my expectation from any reasonably competent compiler, since it is just a wrapper around a simple pointer and a destructor that calls delete
, so the machne code generated by the compiler for:
x *p = new X;
... do stuff with p.
delete p;
and
unique_ptr<X> p(new X);
... do stuff with p;
will be exactly the same code.
Strictly speaking, the answer is no.
Recall that unique_ptr
is a template parametrized not only on the type of pointer but also on the type of the deleter. Its declaration is:
template <class T, class D = default_delete<T>> class unique_ptr;
In addition unique_ptr<T, D>
contains not only a T*
but also a D
. The code below (which compiles on MSVC 2010 and GCC 4.8.1) illustrates the point:
#include <memory>
template <typename T>
struct deleter {
char filler;
void operator()(T* ptr) {}
};
int main() {
static_assert(sizeof(int*) != sizeof(std::unique_ptr<int, deleter<int>>), "");
return 0;
}
When you move a unique_ptr<T, D>
the cost is not only that of copying the T*
from source to target (as it would be with a raw pointer) since it must also copy/move a D
.
It's true that smart implementations can detect if D
is empty and has a copy/move constructor that doesn't do anything (this is the case of default_delete<T>
) and, in such case, avoid the overhead of copying a D
. In addition, it can save memory by not adding any extra byte for D
.
unique_ptr
's destructor must check whether the T*
is null or not before calling the deleter. For defalt_delete<T>
I believe, the optimizer might eliminate this test since it's OK to delete a null pointer.
However, there is one extra thing that std::unique_ptr<T, D>
's move constructor must do and T*
's doesn't. Since the ownership is passed from source to target, the source must be set to null. Similar arguments hold for assignments of unique_ptr
.
Having said that, for the default_delete<T>
, the overhead is so small that I believe will be very difficult to be detected by measurements.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With