牧光小院

被约束的日日夜夜,停不下来的时间。

C++默认会为我们做些什么工作?

2005年5月份,Scott Mayers发布了《Effective C++》第三版。作者根据当前C++的特点和设计模式,对第二版中半数以上的内容作了更新。此等佳作,不敢独享,以肆同好。


什么时候一个空的class不是空的?C++会在何时做些什么事情?如果你不声明它们,编译器会为你声明它们自己的拷贝构造函数、一个赋值运算符和一个析构函数。另外,如果你不声明一个构造函数,编译器还会为你创建一个。所有这些自动生成的函数都是publicinline的。例如,如果你写下:

class Empty {};

这和你写下如下的代码本质上是一样的:

class Empty {

    Empty() {…} // default constructor

    Empty(const Empty& rhs) {…} // Copy constructor

    ~Empty() {…}  // destructor - whether it's virtual?

   

    // copy assignment operator

    Empty& operator=(const Empty& rhs) {}

}

当然,这些函数只有它们真正被需要的时候才会被创建。下面这些情况会使得这些函数被创建:

Empty e1; // default constructor & destructor

Empty e2(e1); // copy constructor

e2 = e1; // copy assignment operator

既然编译器会为你创建这些函数,那么这些函数都做些什么工作呢?默认的构造和析构函数主要是让编译器放置一些执行“幕后工作”的代码,例如调用基类和非静态数据成员的构造和析构函数等。需要注意的是编译器为你生成的这个析构函数并不是虚拟的,除非这个类的基类明确声明了一个虚拟的析构函数。

对于拷贝构造函数和赋值运算符,编译器生成的版本只是简单的copy每一个非静态数据成员。例如,考虑一个名为NamedObject的模板,它可以让你把名字和类型T关联起来。

template <typename T>

class NamedObject {

public :

    NamedObject(constchar* name, const T& value);

    NamedObject(const std::string& name, const T& value);

 

private :

    std::string nameValue;

    T objectValue;

};

由于NamedObject中声明了构造函数,编译器便不会再自做主张为你生成一个默认的。这是很重要的。这意味着如果你精心设计的类的构造方式,你就不用再去担心编译器会愚蠢的为你添加一个不带参数的构造函数而破坏你的设计。

NamedObject 中既没有声明拷贝构造函数也没有声明赋值运算符,所以当需要的时候,编译器会自动为你生成。显然,下面的代码需要拷贝构造函数的支持:

NamedObject<int> no1("Smallest Prime Number", 2);

NamedObject<int> no2(no1);

编译器生成的拷贝构造函数必须要使用no1.nameValueno1.objectValue来初始化no2中对应的成员。由于nameValue的类型是string,并且标准的string有一个拷贝构造函数,所以no2.nameValue就可以通过string的拷贝构造函数完成。另外objectValue是一个整数,对于这个内置类型,简单的bit-copy就可以完成复制的任务了。

其实,如果需要的话,编译器会按照和上面提到的相同的手法来为NamedObject来生成一个赋值运算符。但是,只有当生成的代码在语法和语义都都正确的时候,编译器才会为你执行生成工作,如果其中任何一方面除了问题,编译器就会拒绝为你重载operator =

例如:如果我们这样定义NamedObject

template <typename T>

class NamedObject {

public :

    NamedObject(const std::string& name, const T& value);

 

private :

    std::string& nameValue;

    const T objectValue;
};

之后,下面的代码会怎样呢?

std::string newDog("Persephone");

std::string oldDog("Satch");

NamedObject<int> p(newDog, 2);

NamedObject<int> s(oldDog, 36);
p = s; // What should happen?

在复制前,p.nameValues.nameValue分别指向不同的string对象。这个复制应该对p.nameValue做怎样的改变呢?直觉上,p.nameValue将会指向s.nameValue所指的string对象。但是这破坏了C++的一条基本的准则,C++不允许引用指向不同的对象。换句话说,难道改变p.nameValue所引用的对象应该要影响到其它对象所引用的字符串吗?这是编译器生成的赋值运算符应该做的事情吗?

C++ 对于这个问题的解答方法是拒绝编译这样的代码。如果你想让含有引用数据成员的类支持赋值功能,那么你就必须自己定义赋值运算符。对于含有const数据成员的类来说,故事是类似的。修改对象中的const成员总是非法的,所以编译器对于如何处理这种问题一无所知。最后,如果基类把operator=声明为private,那么编译器同样会拒绝为派生类生成operator=。毕竟,一方面,即便编译器可以生成,operator=也只能处理派生类中属于基类的那一部分;另一方面,派生类也根本无权访问基类中的private成员。

时时刻刻让自己记住

l          编译器会在必要的时候隐式生成类的默认构造函数、拷贝构造函数、operator=和析构函数。

posted on 2005-11-03 09:17 nacci 阅读(2047) 评论(2)  编辑 收藏 引用 所属分类: C++漫谈

评论

# re: C++默认会为我们做些什么工作? 2005-11-06 00:46 guest

这个第二版就有了。  回复  更多评论   

# re: C++默认会为我们做些什么工作? 2006-05-06 18:42 g

hhjhj  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(2)

随笔分类

收藏夹

大家的声音

积分与排名

最新评论

阅读排行榜

评论排行榜