【问题标题】:Why does an explicitly defaulted destructor disable default move constructor?为什么显式默认析构函数会禁用默认移动构造函数?
【发布时间】:2022-01-09 09:21:48
【问题描述】:

为什么显式默认析构函数会禁用类中的默认移动构造函数?我知道它确实,正如几个现有答案中所解释的那样。 (例如Explicitly defaulted destructor disables default move constructor in a class

我想知道为什么:当它实际上并没有做任何暗示移动构造函数可能需要自定义代码的事情时,这样做的理由是什么?事实上,我们建议人们使用=default 而不是空的主体,因为这样编译器就知道除了自动操作之外它不会做任何事情,就像它是在没有任何声明的情况下自动生成的一样。

真的很清楚,我知道为什么用你自己的逻辑定义析构函数应该抑制自动生成。我指出=default 与让编译器隐式生成它相比并没有改变任何东西,所以这个原因在这里不适用。

我记得 >10 年前有很多关于什么是指定它的正确方法的讨论。我不记得,如果我知道的话,为什么它最终会变成这样。有没有人知道为什么这本身就是一个特定的理想功能的令人信服的原因,或者为什么它应该是这样的一些技术原因?

演示:https://gcc.godbolt.org/z/86WGMs7bq

#include <type_traits>
#include <utility>
#include <string>
#include <iostream>


struct C
{
    std::string s;
    C () : s{"default ctor"} {}
//    ~C() = default;  // <<< comment this line out, and we see that original.s has its contents "stolen"
                       // <<< with this dtor declared, the original string is copied.
};

int main()
{
    C original;
    original.s = "value changed";
    C other { std::move(original) };
    using std::cout;
    cout << "original now: " << original.s << '\n';
    cout << "other is: " << other.s << '\n';
}

这条规则在cppreference中有说明,前几天在刚刚发布的一个C++会议视频中提到过,这让我想起了它。

【问题讨论】:

  • 你能提供代码来证明这一点吗?因为I have code saying otherwise.
  • @JDługosz 问题在于您将析构函数设为私有。如果您将访问权限更改为公共,则该类将是可移动构造的。
  • std::is_move_constructible 不是检查这个的正确方法。来自cppreference:“类型没有移动构造函数,但具有接受 const T& 参数的复制构造函数,满足 std::is_move_constructible。”
  • 这可能是一个更好的例子:godbolt.org/z/n6P13z3j3 从程序集中你可以看到它调用了CanMove::CanMove(CanMove&amp;&amp;)CannotMove::CannotMove(CannotMove const&amp;)
  • @JDługosz 这个有用吗?我猜你不是第一个问这个问题的人:stackoverflow.com/a/50490555/4885321

标签: c++ language-lawyer move-semantics move-constructor


【解决方案1】:

我认为最好的理由是一致性:显式默认的复制构造函数(除其他外)禁用隐式移动构造函数,因此最简单的规则是 any 声明相关的特殊成员函数会这样做。

反过来,A(const A&amp;)=default; 产生这种效果的原因是它增加了 表现力 力量:无需繁琐且容易出错的成员初始化器列表,就可以定义保留其资源的类当“移出”时(为其他接收器接口提供异常)。尽管=default 提供的信息并不比它在析构函数上提供的信息多:一般来说,准确地赋予这些构造特殊含义是有意义的,因为它们没有其他效果。如果它们确实有其他含义,那么假设使用它们的程序员除了直接语义之外还想要其他一些行为改变是不安全的。

关于这个主题,为什么要鼓励大家写~A()=default; ~A() {}?两者都没有向编译器传达任何内在信息,并且隐式声明的析构函数的行为通常更可取。

【讨论】:

  • 我认为这与在 .cpp 文件中将特殊成员定义为 A::~A() = default 的能力有关。例如,在这种情况下,标头看起来像A(const A&amp;),编译器无法仅从标头中知道复制构造函数是否默认。因此删除了隐式移动构造函数。但这只是一个疯狂的猜测......
  • @SergeyKolesnik:在其类之外默认的这种析构函数是用户提供的,而不仅仅是用户声明的。它完全等同于A::~A() {},尽管对于复制构造函数来说当然不是这样。
  • 关于析构函数,使用{} 代替=default 会有所不同,因为前者不会“微不足道”。因此,我们鼓励写后者;如果你问为什么空的身体也不能让它变得微不足道,这是一个有趣的问题。
  • @JDługosz:是的,~A()=default;~A() {} 更好,不是因为“编译器知道”更多,而是因为规则注意到了差异。我的观点是你不应该写任何一个——隐式声明的析构函数出于同样的原因更好。
猜你喜欢
  • 1970-01-01
  • 2012-06-15
  • 2011-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-16
  • 1970-01-01
相关资源
最近更新 更多