引言
在 C++ 面向对象编程中,类不仅是封装数据与行为的容器,更是控制资源生命周期的核心。除了常规业务逻辑,实际开发常面临一些特殊约束场景:防止对象拷贝以避免资源重复释放,限定对象创建位置(仅堆或仅栈)以规范内存管理,禁止类被继承以保障核心逻辑不被篡改,或是确保类仅存在一个实例以实现全局资源统一调度。这些需求的实现,正是特殊类设计的核心范畴。
本文将系统拆解五种典型特殊类的实现逻辑与技术细节,结合 C++ 语言特性提供完整代码示例,并深入分析方案的优缺点与适用场景。
设计一个不能被拷贝的类
要实现一个不可拷贝的类,核心思路是禁用拷贝构造函数和赋值运算符重载。根据 C++ 标准版本的不同,有两种主流做法。
在 C++98 时代,通常将这两个函数声明为私有且不定义,这样外部调用时会链接失败。到了 C++11,更推荐使用 = delete 显式删除默认生成的成员函数,语义更清晰且编译期报错更友好。
class NoCopy {
public:
// C++11 推荐写法
NoCopy(const NoCopy&) = delete;
NoCopy& operator=(const NoCopy&) = delete;
};
设计一个只能在堆上创建对象的类
限制对象只能在堆上创建,意味着要阻止栈上的自动构造以及直接 new 之外的其他分配方式。具体策略是将构造函数设为私有,并提供一个静态工厂函数来负责堆内存的分配。
同时,为了防止拷贝导致新的对象被意外创建,必须禁用拷贝操作。
class HeapOnly {
public:
static HeapOnly* CreateObj() {
return new HeapOnly();
}
private:
HeapOnly() {}
~HeapOnly() {}
// 禁止拷贝
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
注意,这里析构函数也建议设为私有或受保护,配合智能指针使用会更安全,但基础实现中保持私有即可。
设计一个只能在栈上创建对象的类
反过来,如果希望对象只能在栈上创建,就需要阻止 new 操作符的使用。这可以通过私有化构造函数并删除全局 operator new 来实现。
由于构造函数私有,外部无法直接实例化,因此需要提供一个静态成员函数返回对象值。这里利用返回值优化(RVO),避免不必要的拷贝。
class StackOnly {
public:
static StackOnly CreateObj() {
return StackOnly();
}
private:
StackOnly() {}
~StackOnly() {}
// 禁止堆分配
void* operator new(size_t) = delete;
void* operator new[](size_t) = delete;
// 禁止拷贝
StackOnly(const StackOnly&) = delete;
StackOnly& operator=(const StackOnly&) = delete;
};
这里删除 operator new 后,该类就无法使用 new 关键字了,因为默认会调用全局的 new。
设计一个不能被继承的类
禁止类被继承同样有两种经典方案。C++98 风格是通过将构造函数设为私有,使得派生类无法访问基类构造函数从而无法实例化。而 C++11 引入了 final 关键字,这是更现代、更直观的方式。
class FinalClass final {
// ... 类内容
};
// class Derived : public FinalClass {}; // 编译错误
设计一个只能创建一个对象的类(单例模式)
单例模式保证系统中该类只有一个实例,并提供全局访问点。常见的实现有饿汉式和懒汉式两种。
饿汉模式
饿汉式在类加载时就初始化实例。优点是线程安全且实现简单;缺点是如果初始化开销大,会影响启动速度,且难以控制不同编译单元间的实例化顺序。
namespace hungry {
class Singleton {
public:
static Singleton& GetInstance() {
return _sinst;
}
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton _sinst;
};
// 静态成员变量定义
Singleton Singleton::_sinst;
}
懒汉模式
懒汉式则延迟到第一次使用时才创建实例。为了支持线程安全,通常需要加锁,但基础版本如下所示。此外,为了在程序结束时正确清理资源,可以借助局部静态对象的析构机制。

namespace lazy {
class Singleton {
public:
static Singleton& GetInstance() {
if (_psinst == nullptr) {
_psinst = new Singleton();
}
return *_psinst;
}
static void DelInstance() {
if (_psinst) {
delete _psinst;
_psinst = nullptr;
}
}
private:
Singleton() {}
~Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* _psinst;
// 辅助类,用于程序退出时销毁单例
class tmp {
public:
~tmp() {
DelInstance();
}
};
static tmp _tmp;
};
Singleton* Singleton::_psinst = nullptr;
Singleton::tmp Singleton::_tmp;
}

一般单例不需要手动释放,因为程序结束时操作系统会自动回收。但在特殊场景下,比如中途需要显示释放,或者程序结束时需要持久化数据,就需要调用析构函数。上述代码中的 tmp 类利用了局部静态对象的析构顺序,保证了在 main 函数结束后、程序终止前执行清理逻辑。


