Может ли оптимизирующий компилятор удалить все затраты времени выполнения из std::unique_ptr?
Читая о std::unique_ptr
на http://en.cppreference.com/w/cpp/memory/unique_ptr мое наивное впечатление, что достаточно умный компилятор может заменить правильное использование unique_ptr
с голыми указателями и просто положить в delete
когда unique_ptr
S уничтожен. Это действительно так? Если да, то делает ли это какой-нибудь основной оптимизирующий компилятор? Если нет, было бы возможно написать что-то с некоторыми / всеми unique_ptr
s преимущества безопасности во время компиляции, которые можно было бы оптимизировать без затрат времени выполнения (в пространстве или времени)?
Примечание для тех, кто (должным образом) обеспокоен преждевременной оптимизацией: ответ здесь не остановит меня от использования 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>
накладные расходы настолько малы, что я считаю, что их будет очень трудно обнаружить с помощью измерений.