6

std::unique_ptrhttp://en.cppreference.com/w/cpp/memory/unique_ptr上阅读,我天真的印象是,足够聪明的编译器可以用裸指针替换正确的使用,并在s 被破坏时unique_ptr放入 a 。真的是这样吗?如果是这样,是否有任何主流优化编译器实际上这样做?如果没有,是否有可能编写具有部分/全部编译时安全优势的东西,可以优化为没有运行时成本(空间或时间)?deleteunique_ptrunique_ptr

请注意那些(适当地)担心过早优化的人:这里的答案不会阻止我使用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 编译时使用 given g++ --std=c++11 -O3 test.ccSmart: 1130859 Dumb: 1130005这意味着智能指针在哑指针的 0.076% 以内,这几乎肯定是噪音。

4

2 回答 2

5

这肯定是我对任何相当称职的编译器的期望,因为它只是一个简单指针的包装器和一个调用 的析构函数delete,因此编译器生成的机器代码用于:

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

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

将是完全相同的代码。

于 2013-07-21T21:59:05.400 回答
4

严格来说,答案是否定的。

回想一下,这unique_ptr是一个模板参数化的不仅是指针的类型,还有删除器的类型。它的声明是:

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

另外unique_ptr<T, D>不仅包含 aT*还包含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;
}

当您移动 a 时unique_ptr<T, D>,成本不仅是T*从源复制到目标的成本(就像使用原始指针一样),因为它还必须复制/移动 a D

确实,智能实现可以检测是否D为空并且具有不执行任何操作的复制/移动构造函数(这是 的情况default_delete<T>),并且在这种情况下,避免了复制 a 的开销D。此外,它可以通过不添加任何额外字节来节省内存D

unique_ptr的析构函数必须T*在调用删除器之前检查 是否为空。我defalt_delete<T>相信,优化器可能会消除这个测试,因为删除空指针是可以的。

然而,还有一件额外的事情是std::unique_ptr<T, D>' 移动构造函数必须做而T*' 没有。由于所有权从源传递到目标,因此源必须设置为空。类似的论点适用于 的赋值unique_ptr

话虽如此,对于default_delete<T>,开销是如此之小,以至于我相信很难通过测量来检测到。

于 2013-07-22T14:43:08.630 回答