【问题标题】:Attempting to delete an initializer list constructor does not always take effect尝试删除初始化列表构造函数并不总是生效
【发布时间】:2021-12-24 03:54:57
【问题描述】:

对于通用标题,我很抱歉,但这是一个令人发指的情况,我无法轻易描述。

假设如下代码:

struct S
{
    S() = default;

    int x;
    int y;
};

S f()
{
    return { 1, 2 };
}

这编译并且工作得非常好。我想禁止它,因为它容易出错(实际代码要复杂得多)。所以,我尝试添加

template<typename T>
S(std::initializer_list<T>) = delete;

但你猜怎么着 - 没有任何变化。使用 std=c++17 在 Visual Studio 2019 上测试。 C++ resharper 将此显示为错误,但 msvc 实际上会编译它并且它可以工作。

等等,现在变得有趣了。如果将S() = default; 替换为S() {},则编译失败并显示

'S::S<int>(std::initializer_list<int>)': attempting to reference a deleted function

好的,这看起来与用户定义的构造函数和初始化有关?!乱七八糟,但还算可以理解。

但是等等——它变得更有趣了——保留= default构造函数,但是使字段private也改变了这种行为,你猜怎么着——错误与不可访问的成员无关,但它再次显示上面的错误!

所以,为了使这个删除工作,我应该将字段设为私有或定义我自己的空构造函数(忽略未初始化的 x 和 y 字段,这只是一个简化的示例),意思是:

struct S
{
    S() = default;
    // S() {}

    template<typename T>
    S(std::initializer_list<T>) = delete;

private:
    int x;
    int y;
};

clang 13 和 GCC 11 的行为方式完全相同,而 GCC 9.3 无法编译原始代码(使用 =default 构造函数,公共字段,但删除了初始化列表构造函数)。

任何想法会发生什么?

【问题讨论】:

  • 如果将 MSVC 更改为使用 C++20 编译会发生什么?在 C++17 中,S 被视为聚合,您使用聚合初始化而不是调用任何类型的构造函数
  • @NathanOliver - 你是对的,C++20 抱怨这个。
  • 那么,什么是禁止这种行为的正确方法呢?为什么public/private 会修改这种行为?
  • 聚合的所有成员的访问说明符必须相同。让他们不同意味着你的班级不再是一个聚合体。如果您可以与成员私下相处,那么我会这样做。如果可以使用 C++20,那就更好了。
  • @NathanOliver 谢谢,你能发布答案吗?我会接受它,完全有道理。不过这很疯狂。

标签: c++ c++11 c++17 initializer-list


【解决方案1】:

在 C++17 中,S 被认为是一个聚合,因此你没有调用任何构造函数,你基本上是直接初始化成员。如果您改用 C++20,S 将不再被视为聚合,因为规则已更改,代码将按预期工作。

更改访问说明符起作用的原因是聚合的所有非静态数据成员的访问说明符需要是公共的。将它们设为非公开意味着您的类不再是聚合,并且您不再获得聚合初始化,而是尝试进行列表初始化并且对于已删除的构造函数失败。

【讨论】:

  • “所有成员”是指所有,对吧——不仅是数据,还有数据+函数?那么不应该在私有部分中添加已删除的构造函数吗?因为它在 VS 中没有任何改变。
  • @KirilKirov 我说错了。只有非静态数据成员需要公开
  • 啊哈,谢谢,有道理。
  • @KirilKirov 我最近做了一些编辑和更正。非静态数据成员需要公开才能成为聚合。它不关心任何构造函数的访问说明符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-15
  • 2012-03-19
  • 1970-01-01
  • 2017-05-02
  • 2018-06-20
相关资源
最近更新 更多