Может ли оптимизирующий компилятор удалить все затраты времени выполнения из std::unique_ptr?

Читая о std::unique_ptr на http://en.cppreference.com/w/cpp/memory/unique_ptr мое наивное впечатление, что достаточно умный компилятор может заменить правильное использование unique_ptr с голыми указателями и просто положить в delete когда unique_ptrS уничтожен. Это действительно так? Если да, то делает ли это какой-нибудь основной оптимизирующий компилятор? Если нет, было бы возможно написать что-то с некоторыми / всеми unique_ptrs преимущества безопасности во время компиляции, которые можно было бы оптимизировать без затрат времени выполнения (в пространстве или времени)?

Примечание для тех, кто (должным образом) обеспокоен преждевременной оптимизацией: ответ здесь не остановит меня от использования std::unique_ptrМне просто интересно, действительно ли это потрясающий инструмент или просто потрясающий.

РЕДАКТИРОВАТЬ 2013/07/21 20:07 EST:

Итак, я проверил с помощью следующей программы (пожалуйста, дайте мне знать, если что-то не так с этим):

#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;
}

Компиляция с g++ 4.7.3 с использованием g++ --std=c++11 -O3 test.cc дал Smart: 1130859 Dumb: 1130005Это означает, что умный указатель находится в пределах 0,076% от тупого указателя, что почти наверняка является шумом.

2 ответа

Решение

Это, конечно, было бы моим ожиданием от любого достаточно компетентного компилятора, так как это всего лишь обертка вокруг простого указателя и деструктора, который вызывает delete, поэтому код машины, сгенерированный компилятором для:

x *p = new X;
... do stuff with p. 
delete p; 

а также

unique_ptr<X> p(new X);
... do stuff with p; 

будет точно такой же код

Строго говоря, ответ - нет.

Напомним, что unique_ptr шаблон, параметризованный не только по типу указателя, но и по типу удалителя. Его декларация:

template <class T, class D = default_delete<T>> class unique_ptr;

К тому же unique_ptr<T, D> содержит не только T* но также D, Код ниже (который компилируется в MSVC 2010 и GCC 4.8.1) иллюстрирует это:

#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;
}

Когда вы двигаете unique_ptr<T, D> стоимость не только копирование T* от источника к цели (как это было бы с необработанным указателем), так как он также должен копировать / перемещать D,

Это правда, что умные реализации могут обнаружить, если D пуст и имеет конструктор копирования / перемещения, который ничего не делает (это случай default_delete<T>) и, в таком случае, избегайте затрат на копирование D, Кроме того, он может сэкономить память, не добавляя лишний байт для D,

unique_ptrдеструктор должен проверить, T* является нулевым или нет перед вызовом удалителя. За defalt_delete<T> Я считаю, что оптимизатор может устранить этот тест, так как это нормально, чтобы удалить нулевой указатель.

Тем не менее, есть еще одна вещь, которая std::unique_ptr<T, D>ход конструктора должен сделать и T*это не так. Поскольку владение передается от источника к цели, источник должен быть установлен в нуль. Аналогичные аргументы справедливы для присваиваний unique_ptr,

Сказав это, для default_delete<T>накладные расходы настолько малы, что я считаю, что их будет очень трудно обнаружить с помощью измерений.

Другие вопросы по тегам