C++中构造函数和析构函数

先上几个问题:
1.构造(析构)函数可以为私有函数么?
2.构造(析构)函数可以为虚函数么?
3.构造(析构)函数可以调用虚函数么?
(以下所有讨论基于gcc 4.1.2的结果)

一、构造(析构)函数可以为私有函数么?

理解问题的关键先清楚以下几个问题:
1.如何保证一个类没有实例?
2.如何保证一个类只有一个实例?
3.如何保证一个类的实例一定在堆上?

1.如何保证一个类没有实例?

把这个类定义为一个抽象类,也就是一个含有纯虚函数的类,关于什么是纯虚函数,我这里直接引用Stroustrup的问题:
What is a pure virtual function?


A pure virtual function is a function that must be overridden in a derived class and need not be defined. A virtual function is declared to be "pure" using the curious "=0" syntax. For example:


 class Base {
 public:
  void f1();  // not virtual
  virtual void f2(); // virtual, not pure
  virtual void f3() = 0; // pure virtual
 };

 Base b; // error: pure virtual f3 not overridden

Here, Base is an abstract class (because it has a pure virtual function), so no objects of class Base can be directly created: Base is (explicitly) meant to be a base class. For example:
 class Derived : public Base {
  // no f1: fine
  // no f2: fine, we inherit Base::f2
  void f3();
 };

 Derived d; // ok: Derived::f3 overrides Base::f3

Abstract classes are immensely useful for defining interfaces. In fact, a class with only pure virtual functions is often called an interface.
You can define a pure virtual function:

 Base::f3() { /* ... */ }

This is very occasionally useful (to provide some simple common implementation detail for derived classes), but Base::f3() must still be overridden in some derived class.
If you don't override a pure virtual function in a derived class, that derived class becomes abstract:

 class D2 : public Base {
  // no f1: fine
  // no f2: fine, we inherit Base::f2
  // no f3: fine, but D2 is therefore still abstract
 };

 D2 d; // error: pure virtual Base::f3 not overridden

 
2.如何保证一个类只有一个实例?

这个也是单例模式的问题,单例模式的要点有三个;一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。这个时候就可以把构造函数定义为私有构造函数,这个时候就不能直接的创建一个对象。还有两种方式来创建一个对象,共有的静态成员函数和通过友元函数或者友元类对象的方法。一种实现方法是设计一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。把构造函数定义成私有的,还可以防止类被继承,因为派生类要调用基类的构造函数(如果基类构造函数为私有的会编译出错)。
class CSingleton

{

//其他成员

public:

static CSingleton* GetInstance()

{

      if ( m_pInstance == NULL )  //判断是否第一次调用

        m_pInstance = new CSingleton();  //以后的调用直接返回第一次调用时候创建好的静态实例指针

        return m_pInstance;

}

private:

    CSingleton(){};

    static CSingleton * m_pInstance;

};

当然也可以根据需求来改进单例模式,可创建多个对象,在类里面多加一个静态成员记录对象的个数。


3.如何保证一个类的实例一定在堆上?

可以把析构函数定义成私有的,如下
class A

 void release(){
  //my work
  this->~A();
 }
 private:
  ~A();
};
int main()
{
 A a;           //编译错误,报错“‘A::~A()’ 是私有的”
 A *b = new A();//OK
}

把类的实例定义在堆上可以使得在函数内部定义的类对象的生命周期改变,在函数外部仍能访问,还可以在析构这个函数前做一些身份验证和自己额外的一些工作。但是这个时候注意,必须有一个额外的函数专门用来析构对象,在这个函数里面调用析构函数。

二、构造(析构)函数可以为虚函数么?

这个问题就比较简单了,构造函数不可以为虚函数,编译器会直接跟你说“构造函数不能被声明为虚函数”。如果对虚函数有一定了解这个原因不难理解,虚函数通过虚表来实现,而指向虚表的指针是在对象空间里面的,正确调用虚函数的顺序是,通过虚指针找到虚表对应的函数。但是,如果构造函数是虚函数,调用之前需要这个虚指针,但偏偏这个虚指针的空间(包含在对象的空间)是要构造函数开辟的,所以就不能为虚函数了。

析构函数可以为虚函数,而且在作为基类的时候很有必要为虚函数,当一个派生类析构的时候,先调用自己的构造函数,然后会调用父类的析构函数,这个父类的析构函数调用就是通过虚函数来调用的。

三、构造(析构)函数可以调用虚函数么?

都可以,但是已经没有了多态性,一般也不能有多态性,而且实在想不出来这样做的理由。“Yes, but be careful. It may not do what you expect. In a constructor, the virtual call mechanism is disabled because overriding from derived classes hasn't yet happened. Objects are constructed from the base up, "base before derived". ”

上代码:

#include<string>
#include<iostream>
using namespace std;

class B
{
public:
  B (const string & ss)
  {
    cout << "B constructor\n";
    f (ss);
  }
  virtual void f (const string &)
  {
    cout << "B::f\n";
  }
};

class D:public B
{
public:
  D (const string & ss):B (ss)
  {
    cout << "D constructor\n";
  }
  void f (const string & ss)
  {
    cout << "D::f\n";
    s = ss;
  }
private:
  string s;
};

int
main ()
{
  D d ("Hello");
}

 

输出结果为:

B constructor
B::f
D constructor

一般情况下用构造函数创建一个对象的过程如下:
1) 首先会按对象的大小得到一块内存(在heap上或在stack上),
2) 把指向这块内存的指针做为this指针来调用类的构造函数,对这块内存进行初始化。
3) 如果对象有父类就会先调用父类的构造函数(并依次递归),如果有多个父类(多重继承)会依次对父类的构造函数进行调用,并会适当的调整this指针的位置。在调用完所有的父类的构造函数后,再执行自己的代码。

派生类D构造对象d的时候,先调用父类的构造函数B(),所以先输出“B constructor”,在父类中调用了一个f()函数,这个是一个虚函数,调用对象类型实际类型为D,按照道理应该会调用D中的f()函数,但是却是输出了“B::f”。其实也不难理解这种特例,因为D的构造函数还没执行,没有对D的数据成员初始化,如果这个时候调用D中f()函数,那么会把"Hello"给一个未初始化的string."D::f() would try to assign its argument to an uninitialized string s. The result would most likely be an immediate crash. "。基于同样的原因,析构函数中的虚函数也没有多态性,当子类调用完自己的析构函数在调用父类的析构函数的时候,如果此时父类的析构函数有虚函数,按照多态性,会继续调用子类的虚函数,然而这个时候,子类对象的数据成员可能已经被上次子类自己的析构函数析构掉了,所以就不会在调用子类的析构函数,而会失去多态性,调用父类自己的虚函数。“Destruction is done "derived class before base class", so virtual functions behave as in constructors: Only the local definitions are used - and no calls are made to overriding functions to avoid touching the (now destroyed) derived class part of the object. ”

析构函数的情况

#include<string>
#include<iostream>
using namespace std;

class B
{
public:
  B (){}
  virtual void f ()
  {
    cout << "B::f\n";
  }
  ~B()
  {
        f();
  }
};

class D:public B
{
public:
  D ()
  {}
  void f ()
  {
    cout << "D::f\n";
  }
  ~D()
  {
        f();
  }
};

int
main ()
{
  D d;
}

 

输出为:
D::f
B::f

 

四、参考:

http://www2.research.att.com/~bs/bs_faq2.html
http://www.cnblogs.com/chio/archive/2007/09/09/887598.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值