在C++中,如果你写下
1 classEmpty{…};
就相当于写下
1 classEmpty{ 2 public: 3 Empty();//default构造函数 4 Empty(constEmpty& rhs){……};//copy构造函数 5 ~Empty(){…}//析构函数 6 Empty&operator=(constEmpty& rhs){…};//copy赋值运算符 7 };
惟有当这些函数被调用时,他们才会被编译器创建出来。
下面代码造成上述每个函数被创建:
1 Empty e1; //default构造函数 2 Empty e2(e1); //析构函数 3 e2=e1; //copy构造函数,copy赋值运算符
如果你设计了一个class,其构造函数要求有实参,你就无需担心编译器会为你添加一个无实参版本而覆盖掉你的版本
1 template<typename T> 2 classNamedObject{ 3 public: 4 NameObject(constchar* name,const T& value); 5 NameObject(const std::string& name,const T& value); 6 private: 7 std::string nameValue; 8 T objectValue; 9 }; 10 NamedObject<int> no1("Smallest Prime Number",2); 11 NamedObject<int> no2(no1);//调用copy构造函数
编译器为NamedObject<int>所生成的copy赋值运算符,其行为基本上与copy构造函数如出一辙,但一般而言只有在生出的代码合法且有合适机会证明它有意义,其表现才会如先前所说。万一条件不符,编译器会拒绝为其生出operator=。
例如:
1 template<typename T> 2 classNamedObject{ 3 public: 4 NamedObject(std::string& name,const T& value); 5 private: 6 std::string& nameValue; 7 const T objectValue; 8 };
考虑下面会发生什么:
1 std::string newdog("Persephone"); 2 std::string olddog("Satch"); 3 NamedObject<int> p(newdog,2); 4 NamedObject<int> s(olddog,36); 5 p=s; //现在p要发生什么事?
赋值前,p.nameValue和s.nameValue都指向string对象。p该如何变化?是reference改变吗?C++不允许让 reference改指向不同的对象。是string对象被修改吗?那么将影响其他持有指针或引用而且指向该string的其他对象。对此,C++拒绝编译那一行赋值动作。
最后还有一种情况: 如果某个基类将copy赋值运算符声明为private,编译器将拒绝为其派生类生成copy赋值运算符。
毕竟编译器为派生类所生的copy赋值运算符想象中可以处理基类成分,但他们当然无法调用派生类无法调用的函数。
而如果你不想使用这些自动生成的成员函数,可以在private成员里面声明他们,而不提供定义。
1 class HomeForSale{ 2 public: 3 …… 4 private: 5 HomeForSale(const HomeForSale); 6 HomeForSale& operator=(const HomeForSale); 7 };
有了上述class定义,当用户企图拷贝HomeForSale对象,编译器会阻挠他,如果你不慎在member函数或friend函数内那么做,轮到链接器发出抱怨。
将链接期错误移至编译期是可能的(而且那是好事,毕竟愈早发现错误愈好),只要将copy构造函数和copy赋值运算符声明为private就可以办到,但不是HomeForSale自身,而是在一个专门为了阻止copying动作而设计的base class非常简单。
1 class Uncopyable{ 2 protected: 3 Uncopyable() {} //允许derived对象构造和析构 4 ~Uncopyable(){} 5 private: 6 Uncopyable(const Uncopyable&); //但阻止copying 7 Uncopyable& operator=(const Uncopyable&); 8 };
参考资料:《Effective C++》