如果需要存储删除器,unique_ptr怎么没有开销?

时间:2018-04-24 12:22:59

标签: c++ c++11 smart-pointers unique-ptr c++-standard-library

首先看看C ++ Primer对unique_ptrshared_ptr所说的内容:
$ 16.1.6。效率和灵活性

  

我们可以确定 shared_ptr不会将删除器视为直接成员,因为删除器的类型在运行时才会知道。

     

因为删除器的类型是unique_ptr类型的一部分,所以删除成员的类型在编译时是已知的。 删除工具可以直接存储在每个unique_ptr对象中。

所以似乎shared_ptr没有删除的直接成员,但是unique_ptr。但是,the top-voted answer of another question说:

  

如果您将删除器作为模板参数提供(如在unique_ptr中),则它是该类型的一部分,您不需要在此类对象中存储任何其他内容。如果将删除器作为构造函数的参数传递(如在shared_ptr中),则需要将其存储在对象中。这是额外灵活性的代价,因为您可以为相同类型的对象使用不同的删除器。

两个引用的段落完全相互矛盾,这让我感到困惑。还有什么,many people says unique_ptr is zero overhead因为它不需要将删除器存储为成员。但是,正如我们所知,unique_ptr的构造函数为unique_ptr<obj,del> p(new obj,fcn),这意味着我们可以将删除函数传递给它,因此unique_ptr似乎已将删除函数存储为成员。真糟糕!

4 个答案:

答案 0 :(得分:31)

std::unique_ptr<T>很可能是零开销(任何理智的标准库实现)。 std::unique_ptr<T, D>,对于任意D,通常不会为零开销。

原因很简单:如果删除器是空的(因而是无状态的)类型(例如std::default_delete实例化),则可以使用空基优化来消除删除器的存储。

答案 1 :(得分:12)

似乎让您感到困惑的关键词是&#34;删除可以直接存储&#34;。但是存储类型为std::default_delete的删除器没有意义。如果您需要,可以创建一个std::default_delete{}

通常,无需存储无状态删除程序,因为您可以按需创建它们。

答案 2 :(得分:10)

Angew's answer非常彻底地解释了发生了什么。

对于那些好奇的事情看起来如何?

template<typename T, typename D, bool Empty = std::is_empty_v<D>>
class unique_ptr
{
    T* ptr;
    D d;

    // ... 
};

template<typename T, typename D>
class unique_ptr<T, D, true> : D
{
    T* ptr;

    // ...
};

专门针对空删除者并利用empty base optimization

答案 3 :(得分:0)

简介:

unique_ptr 可以引入一些小的开销,但不是因为删除,而是因为当你从它移动时,值必须设置为null,如果你使用原始指针你可以留下旧的指针在容易出错但合法的状态下,它仍然指向它之前指向的位置。显然智能优化器可以优化,但不能保证。

回到删除者:

其他答案是正确的,但详细说明。所以这里是简化版本,没有提到EBO或其他复杂的术语。

如果删除器为空(没有状态),则不需要将其保留在unique_ptr中。如果您需要它,您可以在需要时构建它。您需要知道的是删除类型(这是unique_ptr的模板参数之一)。

对于exaple,请考虑以下代码,而不是演示无状态对象的简单创建。

#include <iostream>
#include <string>
#include <string_view>

template<typename Person>
struct Greeter{
    void greet(){
        static_assert(std::is_empty_v<Person>, "Person must be stateless");
        Person p; // Stateless Person instance constructed on demand
        std::cout << "Hello " << p() << std::endl;
    }
    // ... and not kept as a member.
};

struct Bjarne{
    std::string_view operator()(){
        return "Bjarne";
    }
};

int main() {
    Greeter<Bjarne> hello_bjarne;
    hello_bjarne.greet();
}