为什么这段代码在 C++17 中编译没有错误?

vir*_*00x 49 c++ c++17

我已经删除了所有的构造函数,即使下面的代码运行良好。如何以及为什么?

class Ax
{    
    public:
    
    Ax() = delete;
    Ax(Ax const&)=delete;
    Ax(Ax&&)=delete;
    void operator=(Ax const&)=delete;
    void operator=(Ax&&)=delete;

    void print()
    {
        cout << "Hello \n";
    }
};

int main(int argc, char** argv) 
{           
    Ax{}.print();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

dfr*_*fri 43

(有关此主题的完整演练,请参阅博客文章善变的聚合


聚合初始化

Ax是在C ++ 11,C ++ 14和C ++ 17的聚集体,因为它没有用户提供的构造,这意味着Ax{}集合初始化,绕过任何用户声明的构造,即使是删除的。

struct NonConstructible {
    NonConstructible() = delete;
    NonConstructible(const NonConstructible&) = delete;
    NonConstructible(NonConstructible&&) = delete;
};

int main() {
    //NonConstructible nc;  // error: call to deleted constructor

    // Aggregate initialization (and thus accepted) in
    // C++11, C++14 and C++17.
    // Rejected in C++20 (error: call to deleted constructor).
    NonConstructible nc{};
}
Run Code Online (Sandbox Code Playgroud)

什么是聚合类的定义在各种标准版本(C++11 到 C++20)中发生了变化,这些规则可能会产生一些令人惊讶的结果。从 C++20 开始,特别是由于

大多数经常令人惊讶的聚合行为已得到解决,特别是不再允许聚合具有用户声明的构造函数,这是对类作为聚合的更严格要求,而不仅仅是禁止用户提供的构造函数。


用户提供的或仅用户声明的显式默认构造函数

请注意,提供一个显式默认(或删除)的外部定义算作用户提供的构造函数,这意味着在以下示例中,B具有用户提供的默认构造函数,而A没有:

struct A {
    A() = default; // not user-provided.
    int a;
};

struct B {
    B(); // user-provided.
    int b;
};

// Out of line definition: a user-provided
// explicitly-defaulted constructor.
B::B() = default;
Run Code Online (Sandbox Code Playgroud)

结果A是聚合,而B不是。反过来,这B意味着通过空的direct-list-init 初始化将导致其数据成员b处于未初始化状态。A但是,对于,相同的初始化语法将导致(通过A对象的聚合初始化和其数据成员 a 的后续值初始化)其数据成员的零初始化a

A a{};
// Empty brace direct-list-init:
// -> A has no user-provided constructor
// -> aggregate initialization
// -> data member 'a' is value-initialized
// -> data member 'a' is zero-initialized

B b{};
// Empty brace direct-list-init:
// -> B has a user-provided constructor
// -> value-initialization
// -> default-initialization
// -> the explicitly-defaulted constructor will
//    not initialize the data member 'b'
// -> data member 'b' is left in an unititialized state
Run Code Online (Sandbox Code Playgroud)

这可能会让人感到意外,并且存在读取未初始化数据成员b并导致未定义行为的明显风险:

A a{};
B b{};     // may appear as a sound and complete initialization of 'b'.
a.a = b.b; // reading uninitialized 'b.b': undefined behaviour.
Run Code Online (Sandbox Code Playgroud)

  • @Quimby 我之前的整个评论适用于所使用的提供的内容。您正在将论文的主题混合在一起(P1008R1):论文的重点是改变_什么是聚合_而不是_什么算作用户提供_。在 C++20 中,您甚至无法_声明_聚合类的构造函数。这是(已接受的)提案的本质,即任何具有用户声明的构造函数(已删除,默认为未定义)的类永远不应该是聚合。请注意,在 C++20 中,“Foo() = default;”和“Foo() = delete;”都算作用户提供的,但这不再与聚合相关。 (3认同)
  • @Quimby 否。在首次声明时提供的显式删除和显式默认定义不算作用户提供。提供这些显式默认的定义 _out-of-line_ 算作用户提供的,但一个单独的规则是 `= delete` 不得超出范围使用(格式错误),结果是 `= delete` 永远不会计为用户提供。[演示](https://wandbox.org/permlink/fkyGvwhN0ztSaNdY)。 (2认同)

N. *_*ead 12

在 C++17 中,您的示例是一个聚合。对于 C++17 聚合只需要没有用户提供的构造函数;用户声明(但明确删除或默认)的构造函数很好。

在这种情况下,当你执行聚合初始化时Ax{},它不会调用任何构造函数......包括删除的构造函数,因此编译。

在 C++20 中,规则已更改,因此任何用户声明的构造函数都会阻止该类型成为聚合,因此该示例将无法编译。

另见https://en.cppreference.com/w/cpp/language/aggregate_initialization