【问题标题】:Do these two C++ initializer syntaxes ever differ in semantics?这两种 C++ 初始化器语法在语义上是否有所不同?
【发布时间】:2011-02-21 09:34:20
【问题描述】:

假设以下代码是可以正确编译的合法代码,T 是类型名称,x 是变量名称。

语法一:

T a(x);

语法二:

T a = x;

这两个表达式的确切语义是否有所不同?如果有,在什么情况下?

如果这两个表达式确实有不同的语义,我也很好奇标准的哪一部分谈到了这一点。

另外,如果 T 是标量类型的名称时存在特殊情况(又名,intlongdouble,等等...),当 T 是标量时有什么区别类型与非标量类型?

【问题讨论】:

    标签: c++ language-features initializer language-lawyer


    【解决方案1】:

    是的。如果 x 的类型不是T,则第二个示例扩展为T a = T(x)。这要求T(T const&) 是公开的。第一个示例不调用复制构造函数。

    检查可访问性后,可以删除副本(正如托尼指出的那样)。但是,它不能在检查可访问性之前消除。

    【讨论】:

    • 检查复制构造函数的可访问性这一点很重要。可能发生的另一件事(因为我刚刚对其进行了测试)是您有一个“复制构造函数”,它将非常量引用作为参数,这也会导致它失败。
    • @Omnifarious:好点,这是另一个原因。 T(x) 表达式是临时的,不会绑定到 T&。这是故意的,你的T::T(T& victim) 不可能改变x 对象。
    【解决方案2】:

    这里的区别是隐式构造和显式构造,可能有区别。

    想象有一个类型Array 和构造函数Array(size_t length),而在其他地方,你有一个函数count_elements(const Array& array)。这些的目的很容易理解,并且代码看起来足够可读,直到您意识到它允许您调用count_elements(2000)。这不仅是丑陋的代码,而且还会无缘无故地在内存中分配一个 2000 个元素长的数组。

    此外,您可能还有其他可隐式转换为整数的类型,允许您也对这些类型运行 count_elements(),从而以高效率代价获得完全无用的结果。

    您想要在这里做的是将Array(size_t length) 声明为一个显式 构造函数。这将禁用隐式转换,Array a = 2000 将不再是合法的语法。

    这只是一个例子。一旦你意识到explicit 关键字的作用,就很容易想象别人。

    【讨论】:

    • 此外,您可能还有其他类型可以隐式转换为整数,您也可以在这些类型上运行 count_elements() ,从而以高效率代价获得完全无用的结果。 但是,如果我正确理解标准的其他部分,则转换为整数必须是非用户定义的转换,因为在任何转换链中只允许一个用户定义的转换。
    • +1 将隐式/显式带入分析也可以将问题转移到T t(x) vs T t = T(x)....
    • @Omnifarious:您能否将来源链接到您所说的内容?如果这是真的,那对于使用不可见的中间类解决“安全布尔成语”可能会非常有趣。
    • @Mephane:这是我所说的一个例子:paste.lisp.org/+2KI0/1
    • 通过来源我的意思是像 C++ 标准的一部分明确说明这一点,而不是你自己做的例子;特别是因为您的代码确实可以编译,正如我所预料的那样。
    【解决方案3】:

    从 8.5.14 开始(强调我的):

    选择的函数以初始化表达式作为参数被调用;如果函数是构造函数,则调用初始化目标类型的临时值。然后根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;请参阅 class.temporary、class.copy。

    因此,它们是否等效取决于实现。

    8.5.11 也是相关的,但只是在确认可以有所不同:

    -11- 初始化的形式(使用括号或=)通常是无关紧要的,但当被初始化的实体具有类类型时,它就很重要;见下文。只有当被初始化的实体具有类类型时,带括号的初始化程序才能是表达式列表。

    【讨论】:

    • 如果有一个复制构造函数做一些时髦的事情(因此根据大多数人的定义实际上可能甚至不会制作一个副本(是的,一个坏主意,但它仍然可能发生))它仍然可能被淘汰,时髦的非复制的事情不会发生?
    • @Omnifarious:我相信是这样:我很确定我在某处(尽管很多年前)读到,这是标准委员会认为期望等价的一个案例......常见的优​​化好处无论如何,在很大程度上违反直觉且容易出错的代码中超过了潜在的意外因素......
    • 没错。 “允许消除”是规范性措辞。
    • @Tony - 我只是假设 gcc 4.5.1 以这种方式符合标准,我只是对其进行了测试,是的,你是对的,复制被消除了,时髦的非复制事情不会发生。
    • 我想为未来添加一个想法:C++0x 具有移动语义,如果使用得当(STL 也会这样做),它可以非常有效地从临时对象复制即使出现真正的副本,差异也将是可以忽略的。例如,std::vector<int> foo = std::vector<int>(10000, 42) 如果被复制而不初始化为foo,则只需交换内部指针和大小,使临时为空并消除整个复制和实际数组的破坏。当然,用户定义的类类型必须实现适当的移动构造函数。
    【解决方案4】:

    T a(x)直接初始化T a = x复制初始化

    来自标准:

    8.5.11 初始化的形式(使用括号或 =)通常是无关紧要的,但当被初始化的实体具有类类型时,它就很重要;见下文。只有当被初始化的实体具有类类型时,带括号的初始化器才能是表达式列表。

    8.5.12 在参数传递、函数返回、抛出异常 (15.1)、处理异常 (15.3) 和大括号括起来的初始化列表 (8.5.1) 中发生的初始化称为复制初始化,是等价的到表格

       T x = a;
    

    在 new 表达式 (5.3.4)、static_cast 表达式 (5.2.9)、函数表示法类型转换 (5.2.3) 以及基类和成员初始化器 (12.6.2) 中发生的初始化称为直接初始化和相当于形式

        T x(a);
    

    不同之处在于复制初始化会创建一个临时对象,然后用于直接初始化。允许编译器避免创建临时对象:

    8.5.14 ... 然后根据上述规则,调用的结果(对于构造函数的情况是临时的)用于直接初始化作为复制初始化目标的对象。在某些情况下,允许实现通过将中间结果直接构造到正在初始化的对象中来消除这种直接初始化中固有的复制;见 12.2、12.8。

    复制初始化需要一个非显式构造函数和一个可用的复制构造函数。

    【讨论】:

      【解决方案5】:

      在 C++ 中,当你这样写时:

      class A {
        public:
        A() { ... }
      };
      

      编译器实际上会生成这个,具体取决于您的代码使用什么:

      class A {
        public:
        A() { ... }
        ~A() { ... }
        A(const A& other) {...}
        A& operator=(const A& other) { ... }
      };
      

      所以现在您可以看到各种构造函数的不同语义。

      A a1; // default constructor
      A a2(a1); // copy constructor
      a2 = a1; // copy assignment operator
      

      复制构造函数基本上复制所有非静态数据。它们仅在生成的代码合法且健全的情况下才会生成:如果编译器在类中看到他不知道如何复制的类型(根据正常的分配规则),则不会生成复制构造函数。这意味着如果 T 类型不支持构造函数,或者如果类的公共字段之一是 const 或引用类型,例如,生成器将不会创建它们 - 并且代码将不会构建。模板在构建时扩展,因此如果生成的代码不可构建,它将失败。有时它会响亮且非常神秘地失败。

      如果您在类中定义构造函数(或析构函数),生成器将不会生成默认值。这意味着您可以覆盖默认生成的构造函数。您可以将它们设为私有(默认情况下它们是公开的),您可以覆盖它们以便它们什么都不做(有助于节省内存和避免副作用)等等。

      【讨论】:

        猜你喜欢
        • 2014-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多