【问题标题】:Why does this code compile without errors in C++17? [duplicate]为什么这段代码在 C++17 中编译没有错误? [复制]
【发布时间】:2023-03-27 11:48:01
【问题描述】:

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

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;
}

【问题讨论】:

  • 如果您将 Ax{}.print(); 替换为 Ax().print();,则不会。
  • 不使用 gcc10 / C++20 编译但仍然使用 gcc10 / C++17
  • @john 没错,但它也不应该使用统一的初始化语法,即 {}。毕竟它只是构造函数调用。
  • @MartinMorterol 我正在使用 gcc 10.2 + c++17
  • @MartinMorterol confirmed 否则。

标签: c++ c++17


【解决方案1】:

(有关此主题的详细介绍,请参阅博客文章 The fickle aggregate


聚合初始化

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{};
}

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

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


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

请注意,提供显式默认(或已删除)的定义外线计为用户提供构造函数,这意味着在以下示例中,@ 987654327@ 有一个用户提供的默认构造函数,而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;

结果A 是一个聚合,而B 不是。反过来,这意味着通过空 direct-list-initB 进行初始化将导致其数据成员 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

这可能会让人感到意外,并且显然存在读取未初始化数据成员 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.

【讨论】:

  • 所以=delete 算作用户提供?
  • @Quimby 否。在首次声明时提供的显式删除和显式默认定义不算作用户提供。提供这些显式默认的定义out-of-line 算作用户提供,但单独的规则是= delete 不得在外使用(格式错误),结果是= delete 永远不会被视为用户提供的。 Demo.
  • @dfri 好吧,我也是这么想的。但是从论文第 8 页来看,提议的措辞是“没有用户提供的、显式的、用户声明的或继承的构造函数”,因此 =delete 必须算作用户提供,因为提案的下一部分列出了这样的例子“不是聚合”。
  • @dfri 阅读更多,第 8 页底部的三个示例也不允许 B()=default 与您的示例相矛盾,我读错了吗?
  • @Quimby 我之前的全部评论都适用于所提供的内容。您正在混合主题w.r.t。论文 (P1008R1):论文的重点是改变什么是聚合,而不是什么是用户提供的。在 C++20 中,您甚至不能声明聚合类的构造函数。这是(已接受)提案的本质,即任何具有用户声明的 ctor(已删除、默认或未定义)的类都不应成为聚合。请注意,同样在 C++20 中,Foo() = default;Foo() = delete; 都算作用户提供,但这不再与聚合相关。
【解决方案2】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-27
    相关资源
    最近更新 更多