精品文章 论C++构造函数中的不合理设计(1)(3)
不合理普遍特性
下面是一些C++构造函数的不合理设计,当然,可能还有其他一些不合理之处。但是,大多数情况下,我们还是要和这些特性打交道,我们要逐一说明。
1、构造函数可以为内联,但不要这样做
一般来讲,大多数成员函数都可以在前面加入"inline"关键字而成为内联函数,构造函数也不例外,但是别这么做!一个被定义为内联的构造函数如下:
- class x
- {..........
- public : x (int );
- :
- :
- };
- inline x::x(int )
- {...}
在上面的代码中,函数并不是作为一个单独的实体而是被插入到程序代码中。这对于只有一两条语句的函数来说会提到效率,因为这里没有调用函数的开销。
用内联的构造函数的危险性可以在定义一个静态内联构造函数中体现。在这种情况下,静态的构造函数应当是只被调用一次。然而,如果头文件中含有静态内联构造函数,并被其他单元包括的话,函数就会产生多次拷贝。这样,在程序启动时就会调用所有的函数拷贝,而不是程序应当调用的一份拷贝。这其中的根本原因是静态函数是在以函数伪装下的真实对象。
应该牢记的一件事是内联是建议而不是强制,编译器产生内联代码。这意味着内联是与实现有关的编译器的不同可能带来很多差异。另一方面,内联函数中可能包括比代码更多的东西。构造函数被声明为内联,所有包含对象的构造函数和基类的构造函数都需要被调用。这些调用是隐含在构造函数中的。这可能会创建很大的内联函数段,所以,不推荐使用内联的构造函数。
2、构造函数没有任何返回类型
对一个构造函数指定一个返回类型是一个错误,因为这样会引入构造函数的地址。这意味着将无法处理出错。这样,一个构造函数是否成功的创建一个对象将不可以通过返回之来确定。事实上,尽管C++的构造函数不可以返回,也有一个方法来确定是否内存分配成功地进行。这种方法是内建在语言内部来处理紧急情况的机制。一个预定好的函数指针 new-handler,它可以被设置为用户定制的对付new操作符失败的函数,这个函数可以进行任何的动作,包括设置错误标志、重新申请内存、退出程序或者抛出异常。你可以安心的使用系统内建的new-handler。最好的使构造函数发出出错信号的方法,就是抛出异常。在构造函数中抛出异常将清除错误之前创建的任何对象及分配的内存。
如果构造函数失败而使用异常处理的话,那么,在另一个函数中进行初始化可能是一个更好的主意。这样,程序员就可以安全的构件对象并得到一个合理的指针。然后,初始化函数被调用。如果初始化失败的话,对象直接被清除。
3、构造函数不可以被声明为static
C++中,每一个类的对象都拥有类数据成员的一份拷贝。但是,静态成员则没有这样而是所有的对象共享一个静态成员。静态函数是作用于类的操作,而不是作用在对象上。可以用类名和作用控制操作符来调用一个静态函数。这其中的一个例外就是构造函数,因为它违反了面向对象的概念。
关于这些的一个相似的现象是静态对象,静态对象的初始化是在程序的一开始阶段就进行的(在main()函数之前)。下面的代码解释了这种情况。
- MyClass static_object(88, 91);
- void bar()
- {
- if (static_object.count( ) > 14) {
- ...
- }
- }
在这个例子中,静态变量在一开始的时候就被初始化。通常这些对象由两部分构成。第一部分是数据段,静态变量被读取到全局的数据段中。第二部分是静态的初始化函数,在main()函数之前被调用。我们发现,一些编译器没有对初始化的可靠性进行检查。所以你得到的是未经初始化的对象。解决的方案是,写一个封装函数,将所有的静态对象的引用都置于这个函数的调用中,上面的例子应当这样改写。
- static MyClass* static_object = 0;
- MyClass*
- getStaticObject()
- {
- if (!static_object)
- static_object =
- new MyClass(87, 92);
- return static_object;
- }
- void bar()
- {
- if (getStaticObject()->count( ) > 15)
- {
- ...
- }
- }
4、构造函数不能成为虚函数
虚构造函数意味着程序员在运行之前可以在不知道对象的准确类型的情况下创建对象。虚构造函数在C++中是不可能实现的。最通常遇到这种情况的地方是在对象上实现I/O的时候。即使足够的类的内部信息在文件中给出,也必须找到一种方法实例化相应的类。然而,有经验的C++程序员会有其他的办法来模拟虚构造函数。
模拟虚函数需要在创建对象的时候指定调用的构造函数,标准的方法是调用虚的成员函数。很不幸,C++在语法上不支持虚构造函数。为了绕过这个限制,一些现成的方法可以在运行时刻确定构件的对象。这些等同于虚构造函数,但是这是C++中根本不存在的东西。
第一个方法是用switch或者if-else选择语句来手动实现选择。在下面的例子中,选择是基于标准库的type_info构造,通过打开运行时刻类型信息支持。但是你也可以通过虚函数来实现RTTI
- class Base
- {
- public:
- virtual const char* get_type_id() const;
- staticBase* make_object
- (const char* type_name);
- };
- const char* Base::get_type_id() const
- {
- return typeid(*this).raw_name();
- }
- class Child1: public Base
- {
- };
- class Child2: public Base
- {
- };
- Base* Base::make_object(const char* type_name)
- {
- if (strcmp(type_name,
- typeid(Child1).raw_name()) == 0)
- return new Child1;
- else if (strcmp(type_name,typeid
- (Child2).raw_name()) == 0)
- return new Child2;
- else
- {
- throw exception
- ("unrecognized type name passed");
- return 0X00; // represent NULL
- }
- }
这一实现是非常直接的,它需要程序员在main_object中保存一个所有类的表。这就破坏了基类的封装性,因为基类必须知道自己的子类。
一个更面向对象的方法类解决虚构造函数叫做标本实例。它的基本思想是程序中生成一些全局的实例。这些实例只再虚构造函数的机制中存在:
- class Base
- {
- public:
- staticBase* make_object(const char* typename)
- {
- if (!exemplars.empty())
- {
- Base* end = *(exemplars.end());
- list<Base*>::iterator iter =
- exemplars.begin();
- while (*iter != end)
- {
- Base* e = *iter++;
- if (strcmp(typename,
- e->get_typename()) == 0)
- return e->clone();
- }
- }
- return 0X00 // Represent NULL;
- }
- virtual ~Base() { };
- virtual const char* get_typename() const
- {
- return typeid(*this).raw_name();
- }
- virtual Base* clone() const = 0;
- protected:
- static list<Base*> exemplars;
- };
- list<Base*> Base::exemplars;
- // T must be a concrete class
- // derived from Base, above
- template<class T>
- class exemplar: public T
- {
- public:
- exemplar()
- {
- exemplars.push_back(this);
- }
- ~exemplar()
- {
- exemplars.remove(this);
- }
- };
- class Child: public Base
- {
- public:
- ~Child()
- {
- }
- Base* clone() const
- {
- return new Child;
- }
- };
- exemplar<Child> Child_exemplar;
在这种设计中,程序员要创建一个类的时候要做的是创建一个相应的exampler