1、先说结论:
(1)如果使用值传递的话,那么调用拷贝构造函数的时候,首先需要将实参传递给形参,这个传递的时候又要调用拷贝构造函数,这样就会造成无限递归,最后发生栈溢出,但事实上编译器在语法检查上就已经是禁止这种行为的。
(2)如果使用指针传递,这样定义的构造函数实际是一个自定义的有参构造函数,而并非拷贝构造。拷贝构造函数只有一个,如果自定义了拷贝构造函数,编译器直接调用自定义的拷贝构造,否则编译器会自动合成一个。
2、拷贝构造函数被调用的场景有哪些?
(1)用=定义变量时会调用拷贝构造
(2)将一个对象作为实参传递给一个非引用类型的形参
(3)从一个返回类型为非引用类型的函数返回一个对象
(4)用花括号列表初始化一个数组中的元素或者一个聚合类中的成员
3、下面用代码去验证一下:
情况1:(定义一个指针类型的构造函数)
#include<iostream>
using namespace std;
class A
{
public:
A(int a){this->m_a = a;cout<<"call A(int)"<<endl;}
A(const A*tmp){this->m_a = tmp->m_a;cout<<"call A(const A*)"<<endl;}
int get(){return m_a;}
private:
int m_a;
};
A fun1(A a)
{
cout<<a.get()<<endl;
return a;
}
// 测试A(const A*tmp)有没有调用成功
void test1()
{
cout<<"---------THIS IS TEST1--------"<<endl;
A a(2);
A b(&a);
}
// 测试发生拷贝构造的场景会不会调用A(const A*tmp)
void test2()
{
cout<<"---------THIS IS TEST2--------"<<endl;
A a(5);
A b = a;
fun1(b);
}
int main()
{
test1();
test2();
return 0;
}
/*
输出结果:
---------THIS IS TEST1--------
call A(int)
call A(const A*)
---------THIS IS TEST2--------
call A(int)
5
*/
由上面可见,我们定义一个指针类型的构造函数,但是无论在=定义变量的场景,还是在fun1中使用非引用类型的形参以及非引用类型的实参都没有调用A(const A* tmp)这个构造函数,所以A(const A* tmp)仅仅只是一个自定义的有参构造,并不是拷贝构造。
情况2:(定义一个引用类型的构造函数)
#include<iostream>
using namespace std;
class A
{
public:
A(int a){this->m_a = a;cout<<"call A(int)"<<endl;}
A(const A&tmp){this->m_a = tmp.m_a;cout<<"call A(const A&)"<<endl;}
int get(){return m_a;}
private:
int m_a;
};
A fun1(A a)
{
cout<<a.get()<<endl;
return a;
}
int main()
{
A a(5);
A b = a;
fun1(b);
return 0;
}
/*
输出结果:
call A(int) // A a(5)
call A(const A&) // A b = a
call A(const A&) // 调用fun1时实参拷贝到形参
5 // cout<<a.get()<<endl
call A(const A&) // 调用fun1时返回值为非引用类型时发生的拷贝
*/
显然,自定义的A(const A&tmp)才是真正的拷贝构造函数,因为所有会发生拷贝构造的场景都调用了A(const A&tmp)。让我们来回归拷贝构造函数的定义:如果一个构造函数的第一个参数时自身类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造(《C++ Primer》(第五版) P440 )。