【问题标题】:Why is a public copy constructor required even if it is not invoked?为什么即使没有调用公共复制构造函数也需要它?
【发布时间】:2013-12-25 15:54:16
【问题描述】:

拥有一个公共复制构造函数将使这个小程序 编译,但不显示副作用“复制”。

#include <iostream>

class X
{
    public:
    X(int) { std::cout << "Construct" << std::endl; }

    // Having a public copy constructor will make the little program 
    // compile, but not showing the side effect "Copy".

    private:
    X(const X&) { std::cout << "Copy" << std::endl; }

    private:
    X& operator = (const X&);
};

int main() {
    X x = 1;
    return 0;
}

【问题讨论】:

  • X x=1 表示 X x(X(1)) 据我所知,但它已优化为 X x(1);
  • 它是必需的,以便 C++ 代码在实现之间可移植,这些实现可能会或可能不会自行决定执行复制省略。
  • 尝试使用-fno-elide-constructors 标志进行编译。

标签: c++ oop constructor


【解决方案1】:

以下是涉及的 C++ 标准的相关部分:

[dcl.init]/16, bullet 6, sub-bullet 1: 如果初始化是直接初始化,或者如果是复制初始化,其中源类型的 cv 非限定版本与相同的类,或目标类的派生类,构造函数被考虑...... /p>

换句话说,如果编译器优化可以删除副本并不重要,初始化是不正确的,因为没有适用的构造函数。当然,一旦你公开了复制构造函数,下面的部分就适用了:

[class.copy]/31:当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数有副作用.... 这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):

bullet 3:当尚未绑定到引用 (12.2) 的临时类对象将被复制/移动到具有相同 cv-unqualified 类型的类对象时,可以通过构造临时对象直接进入省略复制/移动的目标

【讨论】:

    【解决方案2】:

    您使用了所谓的“复制初始化”(在[decl.init] 中定义)。定义的意思是使用int构造函数构造一个X类型的临时对象,然后使用拷贝构造函数从临时对象中初始化x

    但是,在这种情况下,该标准还允许一种称为“复制构造函数省略”(在[class.copy] 中定义)的优化。如果应用了该优化,则没有临时性。 x 是使用 int 构造函数构造的,就像您编写了所谓的“直接初始化”X x(1);

    为了避免意外编写在应用优化时编译但未应用优化时编译的代码,标准要求复制构造函数必须是可访问的,即使它被省略。因此,构造函数必须是公共的,即使(使用您正在使用的编译器和选项)它没有被调用。

    在 C++11 中考虑了移动构造函数,并且也可以省略。但是这个类X没有移动构造函数,所以C++11和C++03在这个例子中是一样的。

    【讨论】:

    • 简而言之,语义决定了一个副本,因此程序在优化之前应该首先符合语义。
    【解决方案3】:

    我最好的猜测是这是一个编译器优化。如果您有一个复制构造函数,那么以这种方式声明一个对象是有效的。考虑明确地这样做:

    int main() {
        X x(X(1));
        return 0;
    }
    

    或更明确地说:

    int main() {
        X intermediate(1);
        X x(intermediate);
        return 0;
    }
    

    因此,它不使用operator=,而是知道您在声明后尝试初始化您的对象。本质上,编译器是“聪明的”。最后,它再次优化到这一步:

    int main() {
        X x(1);
        return 0;
    }
    

    因此,在编译器弄清楚“你试图做什么”之后,这成为对象的标准初始化。

    编辑

    为了完整起见,请注意,如果您尝试以下操作:

    int main() {
        X x = 1;
        x = 2;
        return 0;
    }
    

    您将看到私人operator= 的问题。明确地,重要的是要注意 operator= 从未在上面的原始初始化问题中实际使用过,即使 = 出现在代码中。

    【讨论】:

    • 哎呀应该去找答案而不是发表评论;)恭喜。
    • @PorkyBrain:如果X x(X(1)) 优化为X x(1),您的上述评论解决了我的担忧,谢谢
    • 这里的代码根本没有使用operator =X x = 1x的直接初始化,不涉及赋值;它可以(但不必)调用复制构造函数。
    • 问题与构造有关,与赋值运算符无关!
    • 没有复制;只有通过 X(int) 构造函数进行初始化。
    【解决方案4】:

    编译器正在将您的初始化优化为:

    X x(1)
    

    这是一种复制省略,标准允许,即使它可以消除您看到的副作用。

    来自 C++03 标准第 12.8 节:

    当满足某些条件时,允许省略实现 类对象的复制构造,即使复制构造函数 和/或对象的析构函数有副作用。在这种情况下, 实现处理省略副本的源和目标 操作只是指相同的两种不同方式 对象,并且该对象的销毁发生在稍后的 如果没有 优化。 111) 这种省略复制操作是允许的 以下情况(可以结合起来消除多个 副本): — 在具有类返回类型的函数中的 areturn 语句中, 当表达式是非易失性自动对象的名称时 具有与函数返回类型相同的 cv 非限定类型, 通过构造自动对象可以省略复制操作 直接进入函数的返回值 — 当临时类> 对象尚未绑定到引用时 (12.2) 将被复制到具有相同 cv 非限定类型的类对象,副本 可以通过构造临时对象省略操作 直接进入省略副本的目标

    第二种情况就是我们这里的情况。

    【讨论】:

    • 这不是作业;这是一个初始化。
    猜你喜欢
    • 2021-11-01
    • 2013-11-23
    • 2011-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-05
    • 2017-03-26
    相关资源
    最近更新 更多