首页 > 解决方案 > c++构造函数和复制构造函数

问题描述

我试图了解以下代码的行为

/* 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)来初始化一个

  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

解决方案


您所看到的在 C++17 之前的编译器中称为复制省略(在编译器资源管理器中使用 C++17或带有vs.标志的wandbox进行尝试)。从 C++17 开始,编译器需要消除许多复制和移动构造函数的情况,并直接构造对象而无需任何中间对象。-std=c++17-std=c++14

不像

A a { 10 };

线

A a = 10;

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

A a = A(10);

在 C++17 之前,允许编译器优化此代码,并a直接从10没有临时对象的情况下构造。请注意,重点是允许不需要执行此复制省略优化。您已经观察到这种允许的优化。

无论编译器是否决定执行复制省略,编译器都必须编译或失败代码。如果编译器无法调用复制构造函数,就像您的情况一样,那么即使它决定进行复制省略,它也必须无条件地使编译失败。这在 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 对象。如果你 make aconst 那么代码将无法编译,当复制构造函数没有得到一个 const 时(对于 C++17 和更早版本):

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

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

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

推荐阅读