【问题标题】:Will using brace-init syntax change construction behavior when an initializer_list constructor is added later?稍后添加 initializer_list 构造函数时,使用大括号初始化语法会改变构造行为吗?
【发布时间】:2014-09-18 17:25:23
【问题描述】:

假设我有这样的课程:

class Foo
{
public:
    Foo(int something) {}
};

我使用以下语法创建它:

Foo f{10};

然后我添加一个新的构造函数:

class Foo
{
public:
    Foo(int something) {}
    Foo(std::initializer_list<int>) {}
};

f 的构造会发生什么?我的理解是它将不再调用第一个构造函数,而是现在调用初始化列表构造函数。如果是这样,这似乎很糟糕。为什么这么多人推荐使用{} 语法而不是() 进行对象构造,而稍后添加initializer_list 构造函数可能会无声无息地破坏事情?

我可以想象这样一种情况,我正在使用 {} 语法构造一个右值(以避免最麻烦的解析),但后来有人向该对象添加了一个 std::initializer_list 构造函数。现在代码中断了,我不能再使用右值构造它,因为我必须切换回() 语法,这会导致最麻烦的解析。这种情况该如何处理?

【问题讨论】:

  • 有些人因为这个和其他原因不推荐它。 (顺便说一句,intializer_list ctor 将是首选。)
  • 没错。它一直在尝试为所有初始化创建一种语法,但如果有多种初始化可以执行的方式,这必然有缺点——必须消除歧义,而且这可能不是完美的。
  • 我会说稍后添加一个具有与其他构造函数相同类型的元素的initializer_list 构造函数是一个糟糕的设计决定。如果必须这样做,您可能应该向其他构造函数添加标签参数或其他内容以消除歧义。
  • @Praetorian 见vector
  • 请参阅this Q&A 以获取标准库中令人惊讶的示例列表。只要确保您不会陷入其中,否则我仍然更喜欢{} 而不是(),因为{} 可以在任何地方使用。

标签: c++ c++11


【解决方案1】:

f 的构造会发生什么?我的理解是它将不再调用第一个构造函数,而是现在调用初始化列表构造函数。如果是这样,这似乎很糟糕。为什么这么多人推荐使用 {} 语法而不是 () 进行对象构造,而稍后添加 initializer_list 构造函数可能会默默地破坏事情?

一方面,初始化列表构造函数和另一个都可行是不寻常的。另一方面,“通用初始化”在 C++11 标准版本中被夸大了,不应该毫无疑问地使用它。

大括号最适合像聚合体和容器一样,所以我更喜欢在围绕一些将被拥有/包含的东西时使用它们。另一方面,括号适用于仅描述如何生成新事物的参数。

我可以想象这样一种情况,我正在使用 {} 语法构造一个右值(以避免最麻烦的解析),但后来有人向该对象添加了一个 std::initializer_list 构造函数。现在代码中断了,我不能再使用右值构造它,因为我必须切换回 () 语法,这会导致最麻烦的解析。如何处理这种情况?

MVP 仅在声明符和表达式之间存在歧义时发生,并且仅在您尝试调用的所有构造函数都是默认构造函数时才会发生。空列表{} 始终调用默认构造函数,而不是具有空列表的初始化列表构造函数。 (这意味着它可以毫无风险地使用。“通用”值初始化是真实存在的。)

如果大括号/括号内有任何子表达式,则 MVP 问题已经解决。

【讨论】:

    【解决方案2】:

    在更新的代码中使用初始化列表改造类听起来很常见。因此,人们在类更新之前就开始对现有构造函数使用 {} 语法,我们希望自动捕获任何旧用法,尤其是那些在模板中可能被忽略的用法。

    如果我有一个像 vector 这样的类需要一个大小,那么可以说使用 {} 语法是“错误的”,但对于过渡,我们无论如何都想抓住它。构造C c1 {val} 意味着为集合获取一些(在这种情况下为一个)值,C c2 (arg) 意味着使用 val 作为类的描述性元数据。

    为了支持这两种用法,当元素的类型恰好与描述性参数兼容时,使用C c2 {arg} 的代码将改变含义。如果我们想支持两种不同含义的形式,在这种情况下似乎没有办法。

    那我该怎么办?如果编译器提供了某种方式来发出警告,我会让带有一个参数的初始化器列表发出警告。这听起来很棘手,更不用说编译器特定的了,所以我会为此制作一个通用模板,如果它还没有在 Boost 中,并推广它的使用。

    除了容器之外,还有哪些其他情况会使用具有不同含义的初始化列表和单参数构造函数,其中单参数与您在列表中使用的类型不是非常不同的类型?对于非容器,注意它们不会因为类型不同或列表总是有多个元素而被混淆就足够了。但如果他们可能以这种方式混淆,最好考虑一下并采取额外的步骤。

    对于使用 initializer_list 功能增强的非容器,专门避免设计可能会出错的单参数构造函数可能就足够了。因此,将在更新的类中删除单参数构造函数,或者初始化列表首先需要其他(可能是标记)参数。也就是说,不要这样做,否则会在代码审查中受到惩罚。

    即使对于类似容器的类,非标准库类的类也可能会导致单参数构造函数形式不再可用。例如。 C c3 (size); 必须写为 C c3 (size, C()); 或设计为也采用枚举参数,这很方便指定初始化为一个值与保留大小,因此您可以争辩它是一个功能并指出以 a 开头的代码单独致电预订。再说一次,如果我能合理避免的话,不要这样做

    【讨论】:

      猜你喜欢
      • 2023-03-05
      • 1970-01-01
      • 2019-08-28
      • 2011-05-06
      • 2013-09-16
      • 2015-12-08
      • 2015-01-24
      • 1970-01-01
      • 2015-09-25
      相关资源
      最近更新 更多