最近提及C++的构造函数与析构函数的一些特性引发了一些思考,不如写篇文章记录下过程。

关键字: C++,构造函数,析构函数,虚函数,纯虚函数,私有函数

构造函数是否可以是私有函数

之前单纯从理论上思考,一个类实例化必然要通过构造函数来产生实例,所以一个类的构造函数如果是私有的,将无法从外部实例化。至此思路都是没有问题的,但是发现自己忘记了一种情况:类的static公有函数可以在没有实例化时即可使用,它自然也能访问到类的私有成员。

所以,类的构造函数可以是私有的,可以通过static静态函数来创建实例。基于这个思路,我们可以在该静态函数中增加一些操作来管理该类的实例化。例如,我们可以通过在静态函数中增加一个计数器限制来设计一个只允许单例的类(如连接到数据库的类),或者指定只能实例化指定数量个数的类。
唯一指针std::unique_ptr的构造函数实现就用到了私有化来实现,如下方代码所示,std::unique_ptr的复制构造函数被设置为私有,从而保证改指针的唯一性。std::move为静态函数,可以不用实例化即可使用,同时它能够访问类中的私有成员,于是复制操作必须通过std::move来实例化新的对象。

std::unique_ptr<A> pA0(new A());
std::unique_ptr<A> pA1(pA0); // 这里会报错
// Test.cpp:22:22: error: calling a private constructor of class 'std::__1::unique_ptr<A,
//      std::__1::default_delete<A> >'
//  std::unique_ptr<A> pA1(pA0);

std::unique_ptr<A> pA2 = std::move(pA0); // 正确用法

特别的,如果构造函数被声明为protected,也能实现相同的功能,区别是声明为protected可以被继承。

析构函数是否可以是私有函数

可以,示例同样是单例类。之前实现的单例类需要要求构造和析构都由自己管理,不允许外部delete析构掉,做法就是将构造函数与析构函数都设为私有,此时构造函数使用方法见上文所述。析构函数由于不再是public,于是外部不能通过delete来析构它。由于栈上的变量会自动析构,所以析构函数为私有函数的类不能在栈上实例化,而堆上的变量通过手动显式调用delete进行析构,所以在堆上可以使用。由于析构函数是私有的,所以需要一个公有成员函数内部进行delete操作,从而提供给外部在堆上进行析构操作。如下方代码所示,A类只允许在堆上使用而不能在栈上使用。

#include <cstdio>
#include <algorithm>

struct A
{
public:
  void Release() { delete this; }
private:
  ~A() {}
};

int main()
{
  A* pA = new A();
  pA->Release(); pA = NULL;

  A a;
  // will cause an compiling error, cause `a` is on the stack, so
  // the destructor will be auto invoked. But the destructor is private.
}

构造函数是否可以是虚函数

不可以,VPTR是由构造函数初始化的,所以在构造函数之前是没有虚函数表(VTABLE)的,也就无法调用虚函数,所以构造函数不可能是虚函数。
另外,从设计上来说,没有构造函数是虚函数这种需求。虚函数目的是为了不同派生类通过调用相同的基类的方法可以执行不同子类的函数,没有哪个类需要通过调用基类的构造函数来执行派生类构造函数的需要。
参考 The C++ Programming Language #597

析构函数是否可以是虚函数

可以,而且经常是虚函数。
因为我们通常销毁一个对象都是通过调用其基类析构函数来销毁,如果析构函数不是虚函数,则不能正确的调用到派生类的析构函数,派生类的资源释放可能就不完全,于是会造成内存泄露的问题。

析构函数是否可以是纯虚函数

不可以,纯虚析构函数不能通过链接器检查。但是,如下方代码所示,A的析构函数被声明为纯虚函数,但是接着做了实现,否则不能通过编译,此时A类变成一个没有纯虚函数的抽象类。如果不实现A的构造函数,则A和它所有的派生类都不能实例化对象,否则会报出链接错误。总而言之,析构函数可以声明为纯虚函数,但是不实现之前,它和它的所有派生类都不能进行实例化。

#include <cstdio>
#include <algorithm>

struct A
{
  virtual ~A()        = 0;  // invoked no matter what
  virtual void func() = 0;  // only invoked if `A::func()` is called
};

A::~A() {}
/* void A::func() {} */

struct B : A
{
  virtual void func() { /* A::func(); */ }
};

int main()
{
  std::unique_ptr<A> pA1(new A());
  // will cause an error, because A is an abstract class

  std::unique_ptr<A> pA2(new B());
}

A destructor can be declared virtual or pure virtual; if any objects of that class or any derived class are created in the program, the destructor shall be defined. If a class has a base class with a virtual destructor, its destructor (whether user- or implicitly-declared) is virtual. (C++11 草案)