Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could an optimizing compiler remove all runtime costs from std::unique_ptr?

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_ptrs 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_ptrs 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.

like image 773
Shea Levy Avatar asked Jul 21 '13 21:07

Shea Levy


People also ask

What happens when unique_ptr goes out of scope?

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.

Can we delete unique_ptr?

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.

Does unique_ptr call Delete?

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.

When should we use unique_ptr?

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.


2 Answers

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.

like image 148
Mats Petersson Avatar answered Sep 16 '22 20:09

Mats Petersson


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.

like image 21
Cassio Neri Avatar answered Sep 16 '22 20:09

Cassio Neri