C++一个不能被继承的类(友元类+类模板)

自从C++11标准出来之后,就有了关键字final可以直接声明一个类不能被继承。那么,在此之前如果想要一个类不能被继承,可能还需要下一番功夫。

1.声明构造函数为私有

如果将构造函数、虚构函数声明为私有的,那么这个类肯定不能被继承。

class A {
private:
A() {}
~A() {}
};

存在的问题
虽然这样的类不能被子类继承,但是本身也无法创建对象。为了解决该问题,可以使用借鉴单例模式的方式,在类中创建好对象。
由于这里讲的不是单例模式,也不追究这个例子是否符合多线程访问了。

class A {
public:
static A* getInstance() {
if (s_inst) {
s_inst = new A();
}
return s_inst;
}
private:
A() {}
~A() {}
static A* s_inst;
};
A* A::s_inst = nullptr;

并不符合要求
这种被限制了的类,显然不是我们想要的。那么如果再改进一下,以满足要求呢?

子类声明为基类的友元类
上面很明确的说明了,构造函数为私有的情况下是无法被继承的。但是有一种方法,可以用“友元类”来打破它。

class B;//Class declaration

class A {
private:
A() {}
~A() {}
friend B;
};

class B : public A {
public:
B() {}
~B() {}
};

以上这种写法有一个问题,就是我们改变了“基类A”让它可以被继承。
但是也造成了一个问题其他类也可以继承“类B”了,因为“B类”的构造函数并不是私有的。

而且我们的目的是写一个不能被继承的类,那还有没有其他的方法呢?

2.虚继承————子类直接调用虚基类的构造函数(私有)

既然基类构造函数为私有的,肯定不能直接继承,这一点是确定的。上一节中,既然“类B”可以被继承,因为它的构造函数是公有的。

那么还有一种方式是“虚继承”。

我们知道“虚继承”有一个特性,一般而言,初始化时子类都会调用直接基类的构造函数从而先初始化基类,但是C++“多继承”时为了避免重复初始化问题,引入了“虚继承”概念————它使用了一套,让最远的子类直接初始化虚基类,其父类不再初始化基类,而避免了重复初始化问题。

class B;//Class declaration

class A {
private:
A() {}
~A() {}
friend B;
};

class B : virtual public A {
public:
B() {}
~B() {}
};

class C : public B { //error
public:
C() {

}
};

对上面的代码解释一下,由于“类B”虚继承自“类A”,而“类C”又继承自“类B”。
那么就会由“类C”直接调用“类A”的构造函数,这一步就失败了……从而造成了“类B”和“类A”这一个整体类不能被继承。

那么,“类C”是否可以“虚继承”自上面这个整体类呢?

答案也是否定的,“类B”之所以可以虚继承“类A”————那是因为声明了“友元类”。
很显然,就算“类C”虚继承以上的任意一个类,都是不可以的,因为不管继承谁,都会由“类C”自己负责调用“类A”的构造函数,这一步始终无法做到。

3.类模板

为了不失一般性,这里引入“类模板”概念。引入该概念之后,“类B”继承“类A”的时候一定要指明类型(比如,A<B>)。

template <typename T>
class A {
friend T;
private:
A() {}
~A() {}
};

class B : virtual public A<B> {

};

那么,这里最终的“类B”是一个不能被继承的类。但是注意,“类A”却变的是可以被继承的。

References:
[1] C++ Primer(第五版)
[2] C++中定义一个不能被继承的类
[3] 面试题28:不能被继承的类
[4] c++设计一个不能被继承的类,为什么必须是虚继承?原因分析
[5] C++ 类模板和模板类