非原创,在学习
考虑下面这个abstract base class声明:
class Abstract_base {
public:
virtual ~Abstract_base() = 0;
virtual void interface() const = 0;
virtual const char*
mumble() const { return _mumble; }
protected:
char* _mumble;
};
你看出什么问题了没有?虽然这个class被设计为一个抽象的 base class (其中有pure virtual function,使得 Abstract_base不可能拥有实体),但它仍然需要一个明确的构造函数以初始化其data member _mumble。如果没有这个初始化操作,其derived class的局部性对象_mumble将无法决定初值。例如:
class Concrete_derived : public Abstract_base {
public:
Concrete_derived();
// ...
};
void foo() {
// Abstract_base::_mumble未被初始化
Concrete_derived trouble;
// ...
}
测试
一般而言,class的 data member应该被初始化,并且只在 constructor中或是在class的其它 member functions中指定初值。其它任何操作都将破坏封装性质,使class的维护和修改更加困难。
C++新手常常很惊讶地发现,一个人竟然可以定义和调用( invoke ) 一个pure virtual function;不过它只能被静态地调用( invoked statically),不能经由虚拟机制调用。例如,你可以合法地写下这段码:
// 定义纯虚函数
// 但是只可能被静态调用
inline void
Abstract_base::interface() const {
std::cout << "Abstract_base::interface() const has been recalled" << std::endl;
}
inline void
Concrete_derived::interface() const {
// 调用纯虚函数
Abstract_base::interface();
std::cout << "Concrete_derived::interface() const has been recalled" << std::endl;
}
这里把前面的代码一整合,子类公有继承父类,测试:
报错,子类中没有声明:virtual void interface() const = 0。给子类中加上这句就没问题了。
一个比较好的替代方案就是,不要把 virtual destructor声明为pure.
一般而言,把所有的成员函数都声明为virtual function,然后再靠编译器的优化操作把非必要的virtual invocation去除,并不是好的设计观念。
不用const
由前面的讨论可知,重新定义 Abstract_base 如下,才是比较适当的一种设计:
class Abstract_base {
public:
virtual ~Abstract_base(); // 不再是pure
virtual void interface() = 0; // 不再是const
const char*
mumble() const { return _mumble; } // 不再是virtual
protected:
Abstract_base(char* pc = 0); // 新增一个有参构造
char* _mumble;
};
考虑这个代码片段:
(1)Point global;
(2)
(3)Point foobar()
(4){
(5) Point local;
(6) Point* heap = new Point;
(7) *heap = local;
(8) // ...
(9) delete heap;
(10) return local;
(11)}
一个object 的生命,是该object的一个执行期属性。local object的生命从L5的定义开始,到L10 为止。 global object的生命和整个程序的生命相同.heap object的生命从它被new运算符配置出来开始,到它被delete运算符摧毁为止。
Point的声明:
typedef struct {
float x, y, z;
} Point;
观念上,编译器会为Point声明一个trivial default constructor、一个trivial destructor、一个trivial copyconstructor,以及一个trivial copy assignment operator。但实际上,编译器会分析这个声明,并为它贴上 Plain Old Data卷标。
POD指的是这样一些数据类型:基本数据类型、指针、union、数组、构造函数是 trivial 的 struct 或者 class。
POD用来表明C++中与C相兼容的数据类型,可以按照C的方式来处理(运算、拷贝等)。非POD数据类型与C不兼容,只能按照C++特有的方式进行使用。
然而,事实上那些trivial members要不是没被定义,就是没被调用,程序的行为一如它在C中的表现一样.
只有一个小小的例外。在C 之中,global被视为一个“临时性的定义”,因为它没有明确的初始化操作。一个“临时性的定义”可以在程序中发生多次.那些实例会被链接器折叠起来,只留下单独一个实体,被放在程序 data segment中一个“特别保留给未初始化之global objects使用”的空间。由于历史的缘故,这块空间被称为BSS,这是 Block Started by Symbol的缩写,是 IBM 704assembler 的一个pseudo-op.
global在C++中被视为完全定义(它会阻止第二个或更多个定义).C和C++的一个差异就在于,BSS data segment在C++中相对地不重要。C++的所有全局对象都被当作“初始化过的数据”来对待。
foobar()函数中的 L5,有一个Point object local ,同样也是既没有被构造也没有被解构。当然啦,Point object local 如果没有先经过初始化,可能会成为一个潜在的程序臭虫(bug),万一第一次使用它就需要其初值的话(如L7)。至于heap object在L6的初始化操作:
new会被转换成
Point* heap = _new(sizeof(Point));
delete会被转换成:
_delete(heap);
以下是 Point的第二次声明,在 public接口之下多了private数据,提供完整的封装性,但没有提供任何virtual function。
class Point{
public:
Point(float x = 0.0, float y = 0.0, float z = 0.0)
:_x(x), _y(y), _z(z) {}
private:
float _x, _y, _z;
};
如果要对class中的所有成员都设定常量初值,那么给予一个explicit initialization list会比较高效(比起意义相同的constructor 的 inline expansion而言)。甚至在 local scope中也是如此。
void test()
{
Point local1 = {1.0, 1.0, 1.0};
Point local2;
local2._x = 1.0; // 注意成员变量是private,外部访问不到,这里有误
local2._y = 1.0;
local2._z = 1.0;
}
local1的初始化操作会比 local2的高效.这是因为当函数的 activation record被放进程序堆栈时,上述initialization list 中的常量就可以被放进local1内存中了.
Explicit initialization list带来三项缺点:
观念上,我们的 Point class有一个相关的default copy constructor.copyoperator和 destructor,然而它们都是无关痛痒的(trivial),而且编译器实际上根本没有产生它们.
总结一句话:自己把需要的都写着
我们的第三个Point声明,将为“继承性质”以及某些操作的动态决议( dynamic resolution)做准备。当前我们限制对z成员进行存取操作:
class Point{
public:
Point(float x = 0.0, float y = 0.0)
:_x(x), _y(y) {}
virtual float getZ();
private:
float _x, _y;
};
有了虚函数,有参构造函数会产生膨胀(初始化vtbl)
合成的copy constructor和copy assignment operator也会膨胀。
constructor 可能内带大量的隐藏码,因为编译器会扩充每一个constructor,扩充程度视 class T的继承体系而定。一般而言编译器所做的扩充操作大约如下: