构造、析构、拷贝语义学——导读

问题实例

 
现有abstract base class声明如下:

1
2
3
4
5
6
7
8
class Abstract_base{
public:
virtual ~Abstract_base() = 0;
virtual void interface() const = 0;
virtual const char* mumble() const {return _mumble;}
protected:
char *_mumble;
};

我们可以看出,即使该class不会具象出一个实体,但它仍然需要一个明确的构造函数以初始化其data member _mumble。如果没有这个初始化操作,其derived class的局部性对象_mumble将无法决定初值。

可能会有人认为这是abstract_base的设计者试图让其每一个derived class提供_mumble初值,然而如果是这样,abstract_base必须提供一个带有唯一参数的protected constructor:

1
2
Abstract_base::
Abstract_base(char *mumble_value = 0):_mumble(mumble_value) {}

一般而言,class的data member应当在自身的constructor或其他member functions中被初始化,其他任何操作都将破坏封装性,使class的维护和修改变得更加困难。
当然,你也可以认为该设计并不合理,因为不应当将在abstract class中设定data member,这种思想体现了interface与implementation分离。但在base class中放置共有的data member也是行之有效的真理。


纯虚函数

 
在学习了长时间的C++后我们已经了解,即使是纯虚函数,也可以被定义和调用。只不过它们只能被静态地调用,无法通过虚拟机制调用,例如:

1
2
3
4
5
6
7
8
inline 
void Abstract::interface() const{//定义纯虚函数
...
}
inline
void Concrete_derived::interface() const{
Abstract_base::interface();//静态调用
}

具体是否需要定义和调用纯虚函数只看开发者是否需要,唯一的例外是pure virtual destructor 一定需要被定义。原因在于每一个derived class destructor会被编译器加以扩展,以静态调用的方式调用其“每一个virtual base class”以及“上一层base class”的destructor。在缺乏定义的情况下,会导致链接失败。


虚拟规格

 
把Abstract::base::mumble()设计为一个virtual function是不明智的,因为其函数定义内容并不与类型有关(并不因为class的类型而形成不同的操作),因而几乎不会被后继的derived class override。此外,由于它的non-virtual函数实体是一个inline函数,如果常常被调用,效率上恐怕会出现问题。
我们认为,无需对不必要声明为virtual的函数实行虚拟化,其弊端在Effective C++中亦有提及。


虚拟规格中const的存在

 
考虑virtual function的const属性很麻烦,因为我们也无法了解未来的需求。如果不把函数声明为const,则意味着该函数不能够获得一个const reference或const pointer,这似乎也不是什么缺点,所以我们提倡将所有的virtual function设置为const。


重新考虑class的声明

 
经过前面的讨论,我们将Abstarct_base重新定义如下:

1
2
3
4
5
6
7
8
9
class Abstract_base{
public:
virtual ~Abstract_base();//放弃纯虚
virtual void interface() = 0;//不再是const
const char* mumble() const {return _mumble;}//不再是virtual
protected:
Abstract_base(char *pc=0);
char *_mumble;
};