unique_ptr要存储deleter怎么可能没有开销呢?

How can unique_ptr have no overhead if it needs to store the deleter?

先看看C++ Primer对unique_ptrshared_ptr说了什么:
16.1.6 美元。效率和灵活性

We can be certain that shared_ptr does not hold the deleter as a direct member, because the type of the deleter isn’t known until run time.

Because the type of the deleter is part of the type of a unique_ptr, the type of the deleter member is known at compile time. The deleter can be stored directly in each unique_ptr object.

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

If you provide the deleter as template argument (as in unique_ptr) it is part of the type and you don't need to store anything additional in the objects of this type. If deleter is passed as constructor's argument (as in shared_ptr) you need to store it in the object. This is the cost of additional flexibility, since you can use different deleters for the objects of the same type.

引用的两个段落完全矛盾,让我很困惑。更重要的是,many people says unique_ptr is zero overhead 因为它不需要将删除者存储为成员。但是,我们知道,unique_ptr有一个unique_ptr<obj,del> p(new obj,fcn)的构造函数,也就是说我们可以给它传递一个deleter,所以unique_ptr好像把deleter存储为一个成员。真是一团糟!

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

原因很简单:空基优化可用于消除删除器的存储,以防它是空的(因此是无状态的)类型(例如 std::default_delete 实例化)。

似乎让您感到困惑的关键词是"The deleter can be stored directly"。但是没有必要存储 std::default_delete 类型的删除器。如果你需要一个,你可以创建一个 std::default_delete{}

一般情况下,无状态删除器不需要存储,可以按需创建。

非常彻底地解释了发生了什么。

对于那些好奇事物在幕后的样子的人来说

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

简介:

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

返回删除器:

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

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

例如考虑以下代码,同时还演示了按需创建无状态对象的简单方法。

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