C ++构造函数和复制构造函数

时间:2019-04-11 02:39:46

标签: c++ c++11

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

/* 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 < / strong>

  2. 直接使用构造函数 A(int)

  3. 初始化 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),因此我定义了一个,但运行输出显示右值副本构造函数wasn没打电话,为什么?

3 个答案:

答案 0 :(得分:5)

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

不一样

A a { 10 };

线

A a = 10;

意味着首先构造一个临时对象,就像代码具有:

A a = A(10);

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

无论决定是否执行复制省略,编译器都必须编译或使代码失败。如果编译器无法像您的情况那样调用复制构造函数,则即使决定进行复制省略,它也必须无条件地使编译失败。这种情况在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中)。

在这种情况下,使用复制省略功能甚至都不会尝试调用复制或move构造函数。

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

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))));

答案 1 :(得分:1)

代码运行时

A a = 10;

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

尝试将其强制转换为int的原因是,您只有两个构造函数:一个使用int,另一个使用引用来引用'A'类型的对象。由于它已经将10强制转换为类型为'A'的对象,并且该对象是一个右值,因此仅说明没有构造函数可以接受该对象。

请注意:这是在不修改对象时始终按const引用传递的提醒。

答案 2 :(得分:1)

写作时

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 &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?

因为不能将临时对象绑定到左值引用。它们只能绑定到右值引用或常量左值引用。 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编译它,然后您可以看到对rvalue构造函数的调用。见下文。

#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