【问题标题】:Why does copy initializaton require destructor in C++17 with guaranteed move/copy elision?为什么复制初始化需要 C++17 中的析构函数并保证移动/复制省略?
【发布时间】:2021-03-24 22:35:08
【问题描述】:

以下代码使用 MSVC (/permissive-) 编译,但无法使用 GCC/Clang 编译 m_ptr1 和 m_ptr2。

#include <memory>

struct ForwardDeclared;

class A {
    public:
        explicit A();
        ~A();
    private:
        std::unique_ptr<ForwardDeclared> m_ptr1 = nullptr;    // not ok
        std::unique_ptr<ForwardDeclared> m_ptr2 {std::unique_ptr<ForwardDeclared>{}};    // not ok
        std::unique_ptr<ForwardDeclared> m_ptr3 {nullptr};    // ok 
        std::unique_ptr<ForwardDeclared> m_ptr4;              // ok
};

int main() {
    A a;
    return 0;
}

Code at compiler-explorer

我的理解是= 符号导致copy initialization,但是,感谢copy elision,我希望m_ptr2 仍然会被初始化而不会失败。

为什么m_ptr2 需要 ForwardDeclared 的析构函数,而 Clang/GCC 对此是否正确? (奖励:得出 m_ptr1 被 MSVC 错误接受的结论是否正确?)

编辑: 用 clang 记录了一个关于这个问题的错误:https://github.com/llvm/llvm-project/issues/54291

【问题讨论】:

  • 请注意,使ForwardDeclared 成为完整类型fixes the compilation problem
  • @SergeyKalinichenko 但是我知道,这也会增加需要包含的代码量
  • 可能是a related bug
  • @xskxzr 有趣的错误,然而,乍一看它看起来不同。在第一个示例中,它使用客户删除器。此错误也记录在 libstdc++ 中,而 MSVC 和 Clang 使用不同的标准库。 (我强制 libc++ 使用 clang)
  • 我重新检查了您的问题(因为我的第一条评论是错误的......),我不认为这是 xskxzr 在这里链接的那种错误 - 我仍然怀疑它根本不是一个错误(发生几乎从来没有当 MSVC 很好时,clang 和 gcc 都是错误的......)。我不完全明白,godbolt如何处理不完整的符号。是否有隐藏设置?您是否在真实项目中重现了这个问题? PS:m_ptr2其实不是直接初始化的吗?

标签: c++ c++17 language-lawyer copy-elision copy-initialization


【解决方案1】:

CWG 2426 特指在这种情况下可能调用的析构函数:

如果调用或按照 7.6.2.8 [expr.new]、8.7.4 [stmt.return]、9.4.2 [dcl.init.aggr]、11.9.3 [class .base.init] 和 14.2 [except.throw]。如果可能调用的析构函数被删除或无法从调用的上下文中访问,则程序是非良构的。

上面的列表中没有提到11.9.2 [class.expl.init] 部分,因此似乎不在可能调用析构函数的情况下。

这提出了一个问题,是否确实如@Fedor 所说,MSVC 是错误的,而 GCC 和 Clang 是正确的。从表面上看,它似乎是相反的。

请注意,cppreference on copy elision 并不认为所有复制省略的情况都要求析构函数可见,它仅指以下情况:

在 return 语句中,当操作数是与函数返回类型相同的类类型(忽略 cv 限定)的纯右值时 [...] 返回类型的析构函数必须在返回点可访问声明和非删除,即使没有 T 对象被破坏。

【讨论】:

【解决方案2】:

我的理解是 = 符号会导致复制初始化,但是,由于复制省略,我希望 m_ptr2 仍然会被初始化而不会失败。

复制省略要求该类型的析构函数可访问且不可删除,即使没有对象被销毁,请参阅https://en.cppreference.com/w/cpp/language/copy_elision

所以 GCC 和 Clang 正确检查了析构函数,这对于不完整类型 ForwardDeclared 无效。

奖励:得出 m_ptr1 被 MSVC 错误接受的结论是否正确?

是的,这里的 MSVC 不正确。

请参阅Why is public destructor necessary for mandatory RVO in C++?,了解为什么强制复制省略不适用。

为什么 m_ptr2 需要 ForwardDeclared 的析构函数并且 Clang/GCC 对此是否正确?

同样的推理也适用于复制省略的有效析构函数的必要性。

【讨论】:

  • 在返回值的情况下,可能会根据timsong-cpp.github.io/cppwp/n4868/stmt.return#2.sentence-7 调用析构函数,但在相同类型的纯右值初始化器的复制省略规则中,我看不到任何等效要求:@987654324 @。在返回语句中复制省略的情况下,cppreference 也明确提到了这一点。
猜你喜欢
  • 2018-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-03
相关资源
最近更新 更多