微不足道的破坏的意义

18
在C++17中,新的std::optional规定,如果T是平凡析构的,则必须使其平凡析构[optional.object.dtor]:

~optional();
1 效果:如果is_trivially_destructible_v<T> != true并且*this包含一个值,则调用val->T::~T()
2 备注:如果is_trivially_destructible_v<T> == true,则此析构函数应该是平凡析构函数。

因此,这个潜在的实现片段不符合标准。
template <class T>
struct wrong_optional {
    union { T value; };
    bool on;

    ~wrong_optional() { if (on) { value.~T(); } }
};

我的问题是:这个强制要求有什么优势?大概地说,对于可以轻松销毁的类型,编译器可以发现value.~T()是一个无操作,并且不为wrong_optional<T>::~wrong_optional()生成任何代码。

其他类型可能依赖于给定类型是否是平凡可销毁的(例如std::vector的销毁)。 - Jarod42
3个回答

18

std::optional 已经有 constexpr 构造函数。当它的析构函数是平凡的时,它是一个字面类型。只有字面类型的对象可以在常量表达式中创建和操作。


如果测试微不足道的破坏性是为了查看它是否为字面类型,那么为什么不使用std::is_literal_type呢? - Lenz
当引入std::optional时,std::is_literal_type被弃用了。(在C++20中,std::is_literal_type已被移除。) - Yongwei Wu
@YongweiWu 这对答案没有影响。 - cpplearner
@cpplearner 抱歉,我本来是想回复第一条评论的。 - Yongwei Wu

14

一个类型被认为是平凡析构类型就是它的奖励。以下是拥有平凡析构函数的优点:

  1. 该类型可以是平凡可复制的。这使得该类型有资格进行各种优化。Visual Studio标准库实现有许多用于处理此类类型的优化

  2. 不调用平凡析构类型的析构函数是合法的。您只需释放其存储空间即可。这是一种低级操作,但它具有优势。这是实现上述优化的一部分。

  3. 带有平凡析构函数的类型可以是字面类型,并且因此是可以在编译时构造和操作的对象。

optional<T>的接口尽可能地不干扰T的行为。因此,如果您可以使用T执行某些操作,则应该能够使用optional<T>执行相同的操作,除非有一个非常好的理由不这样做。


关于(1),std::optional<T>虽然不强制要求是平凡可复制的,但您认为它应该是吗? - Barry
@Barry:更好的问题是“它能吗?”答案是否定的,它不能。optional实际上包含一个“布尔值”和一些存储sizeof(T)字节的内容,这些内容可能已经初始化为包含一个对象,也可能没有。如果传入的存储包含一个对象,仅仅进行位拷贝不会导致目标存储包含一个对象。如果T是平凡析构的,则optional仍然可以被轻松地析构,但它不能被轻松地复制。 - Nicol Bolas
2
如果 T 是可以平凡复制的,那么你可以实现 optional<T> 为可以平凡复制。 - Barry
@Barry:你哪部分没明白“答案是否定的”这句话啊 ;) - Nicol Bolas
@Barry:我已经详细解释了为什么你不能这样做。你没有解释为什么这种推理是错误的。如果你想学习更多,请阅读[basic.types]/2和3。 - Nicol Bolas
显示剩余3条评论

4

根据这个问题/答案,值得一提的一个还未被提到过的具体原因是: 一个类型被认为是平凡可复制和平凡析构时,它可以在ABI中通过寄存器传递(参见Agner Fog的调用惯例)。这可能会对生成的代码产生重大影响。例如一个简单的函数:

std::optional<int> get() { return {42}; }

如果类型不是可平凡复制/析构的,则可能会发出以下代码:
    mov     rax, rdi
    mov     DWORD PTR [rdi], 42
    mov     BYTE PTR [rdi+4], 1
    ret

如果只是需要发出以下内容,则可以这样做:
    movabs  rax, 4294967338
    ret

那肯定更好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接