关于构造函数,析构函数,explicit等内容

一.编译器会为我们做什么
   当声明一个空类的时候,编译器会 根据需要生成:默认构造函数,拷贝(copy)构造函数,赋值操作符重载函数(copy assignment),析构函数.上面四个函数,当你声明一个类的时候,你没有定义的时候,编译器会根据需要帮你生成, 他们都是 public 的并且是 inline .注意:只有这些函数被调用的时候,编译器才会主动帮你生成.对于copy assignment编译器还会检查生成的代码是否合法,如果不合法,编译器拒绝生成.
    对于copy构造函数和copy assignment操作符,编译器创建的版本只是单纯的将来源对象的每一个 non-static成员变量拷贝到目标对象.这在很多情况下会引起错误.
例如:
template<class T>
class NamedObject {
public:
NamedObject(std::string& name, const T& value);
 ...          // 假设没有声明任何 operator=
private:
  std::string& nameValue;      // 现在是一个引用
  const T objectValue;          // 现在为 const 的
};

当如下使用的时候:
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2); 
NamedObject<int> s(oldDog, 36);
p = s;     // 对于 p 中的数据成员将会发生     // 什么呢?

注意p=s这句.它是什么含义呢?
1.p.nameValue应该指向s.nameValue所指向的那个String么?这显然是不符合C++语法规则的.C++不允许"引用改指不同的对象",引用一旦赋值就不能更改.
2.p.nameValue所指向的那个String对象应该改变么?如果改变那么会影响"持有pointer或者reference而且指向该String"的其他对象.
因为有很大的不确定性和影响,编译器将拒绝编译p=s这句话,而将决定权交给你.
你如果需要在一个内含有reference成员的类里支持赋值操作(assignment),你必须自己定义赋值操作符(copy assignmen). 面对内含"const 成员"的类,编译器的反应也是一样的.因为更改const成员是不合法的,编译器无法面对给const成员赋值的情况.
还有种情况:当基类(base classes)把赋值操作符(copy assignment)定义为private,编译器将拒绝为子类(derived classes)生成默认的copy assignment.因为编译器为子类创建的copy assignment会调用 base classes的 copy assignment(无访问权限).

如果一个类不想被拷贝,即不支持copy构造函数,copy assinment.有两种方法:
1.自己声明一个对应的private方法,而不去实现它(防止被成员函数或者友元函数调用).这样谁调用它,将会引发一个link错误.
2.定义个类,copy构造函数和copy assignment定义为private.不想实现这一功能的类继承这个类.因为编译器生成的copy构造函数和copy assignment函数会去调用父类的对应函数,因权限问题,会引发编译错误.这样把link时期的错误转移到编译期间.

二.关于在构造函数,析构函数和虚函数
1. 多态的基类的析构函数必须为虚函数.
对工厂模式来说,工厂返回base class的指针,来指向子类对象.如果这个对象位于heap(对工厂模式来说,肯定是).我们要正确的delete掉它.
delete baseclasspointer;
如果baseclass的析构函数不是虚函数,这将会引发错误.因为它会调用base class的析构函数,子类对象部分将不会被释放!一个类被释放一部分,情况是不确定的!

如果一个类有虚函数,其每个对象将会维持一个虚函数表,这将导致对象体积的增大.会引起兼容,移植性的问题.
所以一个心得是:如果有一个virtual函数,才将其析构函数定义为virtual.

2.不要在构造函数和析构函数中使用虚函数!
对于构造函数:会先调用base class的的构造函数,此时对象是base class类型的,而不是derived  class类型.base class 构造期间,虚函数不会下降到到derived class.这样你所期待的多态就落空了.
对于析构函数:会先析构derived class的成员,这样derived class的成员会呈现未定义的状态,C++视他们为不存在.进入base class的析构函数后,对象成为base class类型对象,而C++的虚函数,dynamic_casts(运行期类型信息)也这么看待它.

三.explicit 构造函数
这个主要是防止隐式转换所引起的错误.
隐式转换只会发生在构造函数只有一个参数的情况下.所以两个以上参数就不需要这个关键字.
隐式转换的例子:
class string
{
//...
public:
/**explicit*/ string(int size); // block implicit conversion
string(const char *); //implicit conversion
~string();
};
int main()
{
string s = "hello"; //OK, convert a C-string into a string object
int ns = 0;
s = 1;
}


上面的代码中
string s = "hello";

隐式转换为:
string temp("hello");
string s = temp;

s=1

这种代码,可能是你手贱将ns写成s,这也将编译通过.
还有种情况:
string s = "A"; //string(const char *)
string s1= 'A';//调用string(int size);
所以必须小心隐式转换!!
为什么不让构造函数默认为explicit呢?
很多旧的C++代码依赖隐式转换来实现.所以默认就是隐式转换.

一,二的内容来自于 Effective C++
三的内容来自于:ANSI/ISO C++ Professional Programmer's Handbook 和网络

ANSI/ISO C++ Professional Programmer's Handbook下载地址:
http://ishare.iask.sina.com.cn/f/20517525.html

你可能感兴趣的:(C++)