【问题标题】:c++ constructor and copy constructorc++构造函数和复制构造函数
【发布时间】:2019-04-11 02:39:46
【问题描述】:

我正在尝试理解以下代码的行为

/* code block 1 */
#include <iostream>
class A
{
        private:
            int value;

        public:
            A(int n) { std::cout << "int n " << std::endl; value = n; }
            A(const A &other) { std::cout << " other " << std::endl; value = other.value; }
            // A (A &&other) { std::cout  << "other rvalue" << std::endl; value = other.value; }

            void print(){ std::cout << "print " << value << std::endl; }

};

int main(int argc, char **argv)
{
        A a = 10;
        A b = a;
        b.print();
        return 0;
}

当我编译上面的代码时,它可以正常工作

/* code block 2 */
g++ -std=c++11 t.cpp 
./a.out
int n 
 other 
print 10

当我从复制构造函数中删除 const

/* code block 3 */
class A
{
        ...
        A(int n) { std::cout << "int n " << std::endl; value = n; }
        A(A &other) { std::cout << " other " << std::endl; value = other.value; }
        // A (A &&other) { std::cout  << "other rvalue" << std::endl; value = other.value; }
}

编译器不会编译

/* code block 4 */
t.cpp:19:5: error: no viable constructor copying variable of type 'A'
            A a = 10;
              ^   ~~
t.cpp:9:4: note: candidate constructor not viable: no known conversion from 'A' to 'int' for 1st argument
                    A(int n) { std::cout << "int n " << std::endl; value = n; }
                    ^
t.cpp:10:4: note: candidate constructor not viable: expects an l-value for 1st argument
                    A(A &other) { std::cout << " other " << std::endl; value = other.value; }

从结果t.cpp:9:4看来,编译器试图将A转换为int,但代码是A a = 10;,如果我是编译器,我要么

  1. 尝试从整数10初始化一个类型为A的临时变量,然后使用复制构造函数A(A &other)初始化a

  2. 直接用构造函数A(int)初始化a

我对 t.cpp:9:4

的编译器输出感到困惑

从输出 t.cpp:10:4,编译器说它需要一个左值复制构造函数,所以我将代码更改为

/* code block 5 */
class A
{
        ...
        A(int n) { std::cout << "int n " << std::endl; value = n; }
        A(A &other) { std::cout << " other " << std::endl; value = other.value; }
        A (A &&other) { std::cout  << "other rvalue" << std::endl; value = other.value; }
}

当我按照提示定义右值复制构造函数时,输出显示未调用右值复制构造函数

/* code block 6 */
g++ -std=c++11 t.cpp 
int n 
 other 
print 10

问题:

  1. (在代码块 3 中)为什么我不能从复制构造函数中删除 const
  2. (在代码块 4 -> t.cpp:9:4 中)为什么编译器会尝试从 'A' 转换为 'int'?
  3. (在代码块 5 中)编译器说它需要一个右值复制构造函数(来自代码块 4 -> t.cpp:10:4),所以我定义了一个,但运行输出显示右值复制构造函数是没叫,为什么?

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    您所看到的在 C++17 之前的编译器中称为 copy elision(在 compiler explorerwandbox 上尝试使用 C++17 并使用 -std=c++17-std=c++14 标志)。从 C++17 开始,编译器需要消除许多复制和移动构造函数的情况,并直接构造对象而无需任何中间对象。

    不一样

    A a { 10 };
    

    线

    A a = 10;
    

    表示先构造一个临时对象,好像代码中有:

    A a = A(10);
    

    在 C++17 之前,编译器允许优化此代码,并直接从 10 构造 a 而不使用临时对象。请注意,重点是允许而不是必需来执行此copy elision 优化。您已观察到此允许优化。

    无论编译器是否决定执行复制省略,编译器都必须编译或失败代码。如果编译器无法调用复制构造函数,就像您的情况一样,那么即使它决定进行复制省略,它也必须无条件地使编译失败。这在 C++17 中发生了变化,现在需要编译器在这种情况下进行复制省略优化。由于保证省略了拷贝构造函数,所以甚至不需要拷贝构造函数,代码也能编译无误。

    注意没有 const 的复制构造函数:

    A(A &other) { std::cout << " other " << std::endl; value = other.value; }
    

    没有复制省略,这个复制构造函数不能用于:

    A a = A(10);
    

    不能使用,因为 A(10) 是一个临时对象,因此可以作为右值参数传递给构造函数和方法,如

    A(A && other);
    foo(A && other);
    

    或作为 const 左值引用参数传递给构造函数和方法,如

    A(const A& other);
    bar(const A& other);
    

    但它不能作为常规可变参数传递(就像在代码块 3 中一样)。

    使用复制省略,在这些情况下,它甚至不会尝试调用复制或移动构造函数。

    它仍然需要调用复制构造函数

    A b = a;
    

    它可以使用可变参数来做到这一点,只是因为a 既不是临时对象也不是 const 对象。如果你使a const 代码将无法编译,当复制构造函数没有得到一个const(对于C++ 17 和更早版本):

    const A a = 10;
    A b = a;
    //  ^^  this will fail
    

    有趣的提示:以下行将保证即使使用 C++17 也不会调用复制构造函数:

    A a = A(A(A(A(1))));
    

    【讨论】:

    • @zpoint 因为编译器忽略了副本。如果它看到一个无名的临时对象,它会直接在目标上构造它。当编译器避免不必要的复制构造函数时,这是“复制省略”。请参见最后一个示例:A a = A(A(A(A(1))));。在 C++17 之前,这只是一种优化,但在 C++17 中,这成为了正式行为,不再是单纯的优化。
    • 对不起,评论中的问题应该是'我仍然不明白为什么在代码块6中没有调用右值复制构造函数'(我无法编辑评论,所以我删除并再次发布)
    【解决方案2】:

    当你写作时

    A a = 10;
    

    编译器将 10 转换为临时对象,然后调用复制构造函数创建一个。

    A a = A(10);
    

    考虑一下这个程序,

    #include <iostream>
    class A
    {
            private:
                int value;
    
            public:
                A(int n) { std::cout << "int n " << std::endl; value = n; }
                A(const A &other) { std::cout << " other " << std::endl; value = other.value; }
                //A (A &&other) { std::cout  << "other lvalue" << std::endl; value = other.value; }
    
                void print(){ std::cout << "print " << value << std::endl; }
    
    };
    
    int main(int argc, char **argv)
    {
            A a = 10;
            //A a(1);
            //A b = a;
            //b.print();
            return 0;
    }
    

    并用

    编译它
    g++ t.cpp -std=c++11
    

    在运行程序时,它的输出是

    int n 
    

    现在您可能想知道为什么不调用复制构造函数A(const A &amp;other)。这是因为 C++ 中的 copy elision。编译器可以优化对复制构造函数的调用并直接调用匹配的构造函数。 所以不是A a = A(10);,而是这个A a(10);

    如果你想禁用复制省略,编译上面的程序

    g++ t.cpp -std=c++11 -fno-elide-constructors
    

    现在运行程序你可以看到下面的输出

    int n 
     other 
    

    没有复制省略。所以,A a = A(10); 被调用。首先创建一个临时对象,然后调用复制构造函数创建a

    (in code block 3) why can't I remove the const from copy constructor?
    

    因为临时对象不能绑定到左值引用。它们只能绑定到右值引用或 const 左值引用。 A(10) 创建一个临时对象,该对象只能绑定到 const 左值引用 (const A&) 或右值引用 (A&&)。

    (in code block 5) the compiler says that it need a rvalue copy constructor(from code block 4 -> t.cpp:10:4), so I define one, but the running output show the rvalue copy constructor wasn't called, why?
    

    发生这种情况是因为复制省略。用-fno-elide-constructors 编译它,然后你可以看到对右值构造函数的调用。见下文。

    #include <iostream>
    class A
    {
            private:
                int value;
    
            public:
                A(int n) { std::cout << "int n " << std::endl; value = n; }
                A(A &other) { std::cout << " other " << std::endl; value = other.value; }
                A (A &&other) { std::cout  << "other lvalue" << std::endl; value = other.value; }
    
                void print(){ std::cout << "print " << value << std::endl; }
    
    };
    
    int main(int argc, char **argv)
    {
            A a = 10;
            //A a(1);
            //A b = a;
            //b.print();
            return 0;
    }
    

    编译:

    g++ t.cpp -std=c++11 -fno-elide-constructors
    

    输出

    int n 
    other lvalue
    

    【讨论】:

      【解决方案3】:

      当你的代码运行时

      A a = 10;
      

      它将 10 转换为 A 类型的变量并调用复制构造函数来初始化“a”。因为 10 在内存中没有引用,所以它是一个右值。并且由于您的复制构造函数通过引用接收“其他”,因此您不能传入右值,因为如果修改它,则没有要修改的引用。只有通过 const 引用,C++ 才允许通过引用传入右值。

      它试图将其转换为 int 的原因是您只有两个构造函数:一个接收 int,另一个通过引用接收“A”类型的对象。由于它已经将 10 转换为“A”类型的对象,并且该对象是一个右值,因此它只是说明两个构造函数都不能接受该对象。

      注意:这是一个提醒,当对象没有被修改时,总是通过 const 引用传递。

      【讨论】:

      • “因为 10 在内存中没有引用,所以它是一个左值”——这句话是错误的。左值是指一个对象的身份(它在内存中的位置)。右值用于对象的值。
      • 你是对的,对不起。意思是在所有这些地方说右值。固定。
      猜你喜欢
      • 2013-10-13
      • 2020-05-14
      • 1970-01-01
      • 2013-04-29
      • 1970-01-01
      • 1970-01-01
      • 2016-09-12
      • 2013-12-19
      • 1970-01-01
      相关资源
      最近更新 更多