在C++中, 构造函数,拷贝构造函数,析构函数和赋值函数(赋值运算符重载)是最基本不过的需要掌握的知识。在effective C++中说过这么一点:拷贝构造函数的参数必须是引用类型的。但是为什么呢?
拷贝构造函数的参数必须是引用类型的
如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。
需要澄清的是,传指针其实也是传值,如果上面的拷贝构造函数写成CClass(const CClass* c_class),是可以运行的。但是参数变指针后,已经不是拷贝构造函数了,已经变成了普通的构造函数,程序不会在需要拷贝构造函数的时候自动调用它。
看下面的代码:
#include<iostream> using namespace std; class CExample { private: int m_nTest; public: CExample(int x) : m_nTest(x) //带参数构造函数 { cout << "constructor with argument"<<endl; } // 拷贝构造函数,参数中的const不是严格必须的,但引用符号是必须的 CExample(const CExample & ex) //拷贝构造函数 { m_nTest = ex.m_nTest; cout << "copy constructor"<<endl; } CExample& operator = (const CExample &ex) //赋值函数(赋值运算符重载) { cout << "assignment operator"<<endl; m_nTest = ex.m_nTest; return *this; } void myTestFunc(CExample ex) { } }; int main(void) { CExample aaa(2); CExample bbb(3); bbb = aaa; CExample ccc = aaa; bbb.myTestFunc(aaa); return 0; }
这个例子的输出结果:
constructor with argument // CExample aaa(2); constructor with argument // CExample bbb(3); assignment operator // bbb = aaa; copy constructor // CExample ccc = aaa; copy constructor // bbb.myTestFunc(aaa);
分析:第一个和第二个没有什么特别的地方,就是普通的构造函数。第三个和第四个为什么结果不一样:
原因是, bbb对象已经实例化了,不需要构造,此时只是将aaa赋值给bbb,只会调用赋值函数。但是ccc还没有实例化,因此调用的是拷贝构造函数,构造出ccc,而不是赋值函数。
第五个:实际上是aaa作为参数传递给bbb.myTestFunc(CExample ex), 即CExample ex = aaa;和第四个一致的, 所以还是拷贝构造函数,而不是赋值函数。
通过这个例子, 我们来分析一下为什么拷贝构造函数的参数只能使用引用类型。
看第四个输出: copy constructor // CExample ccc = aaa;
构造ccc,实质上是ccc.CExample(aaa); 我们假如拷贝构造函数参数不是引用类型的话, 那么将使得 ccc.CExample(aaa)变成aaa传值给ccc.CExample(CExample ex),即CExample ex = aaa,因为 ex 没有被初始化, 所以 CExample ex = aaa 继续调用拷贝构造函数,接下来的是构造ex,也就是 ex.CExample(aaa),必然又会有aaa传给CExample(CExample ex), 即 CExample ex = aaa;那么又会触发拷贝构造函数,就这下永远的递归下去。
在C++中,调用拷贝构造函数有三种情况:
1.一个对象作为函数参数,以值传递的方式传入函数体.
2.一个对象作为函数返回值,以值传递的方式从函数返回.
3.一个对象用于给另外一个对象进行初始化(创建对象时初始化).
如果在没有显式声明构造函数的情况下,编译器都会为一个类合成一个缺省的构造函数。如果在一个类中声明了一个构造函数,那么就会阻止编译器为该类合成缺省的构造函数。但是定义了其他构造函数(但没有定义拷贝构造函数),编译器总是会为我们合成一个拷贝构造函数,也就是说自定义的构造函数不会阻止默认的拷贝构造函数。
另外函数的返回值是不是引用也有很大的区别,返回的不是引用的时候,只是一个简单的对象,此时需要调用拷贝构造函数,否则,如果是引用的话就不需要调用拷贝构造函数。
#include<iostream> using namespace std; class A { private: int m_nTest; public: A() { } A(const A& other) //构造函数重载 { m_nTest = other.m_nTest; cout << "copy constructor"<<endl; } A & operator =(const A& other) { if(this != &other) { m_nTest = other.m_nTest; cout<<"Copy Assign"<<endl; } return *this; } }; A fun(A &x) { return x; //返回的不是引用的时候,需要调用拷贝构造函数 } int main(void) { A test; fun(test); system("pause"); return 0; }
什么时候需要自定义拷贝构造函数和赋值符函数.
简单的规则:如果需要定义一个非空的析构函数,那么,通常情况下也需要定义一个拷贝构造函数和赋值符函数.
通常的原则是:
1.对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;
2.在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符,即提供赋值符函数.
当我们知道需要自定义拷贝构造函数和赋值符函数时,就得考虑如何良好的实现它们.
当自定义copying函数(包含拷贝构造函数和赋值符函数)时,需要确保以下两点:
1.复制所有的local成员变量
2.调用所有base classes内的适当的copying函数,完成基类的copying.
下面是一个具体的例子:
class Customer { public: ... Customer(const Customer& rhs); Customer& operator=(const Customer& rhs); ... private: std::string name; }; Customer::Customer(const Customer& rhs):name(rhs.name) { cout << "Customer copy constructor" << endl; } Customer& Customer::operator=(const Customer& rhs) { cout << "Customer copy assignment operator" << endl; name = rhs.name; //疑惑,为什么在copying函数里可以通过对象调用私有变量? return *this; } class PriorityCustomer:public Customer { public: ... PriorityCustomer(const PriorityCustomer& rhs); PriorityCustomer& operator=(const PriorityCustomer& rhs); ... private: int priority; }; PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs):Customer(rhs),priority(rhs.priority) { cout << "PriorityCustomer copy constructor" << endl; } PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs) { cout << "PriorityCustomer copy assignment operator" << endl; Customer::operator=(rhs); //对base class成分进行赋值动作 priority = rhs.priority; return *this; }
上面代码中,通过对象直接访问private的成员变量,似乎违背了对象的封装,具体的分析和理解参考下面的链接: