C++ 特殊类设计
在 C++ 编程中,有时我们需要设计一些特殊的类,这些类具有特定的行为和限制。本文将介绍几种常见的特殊类设计模式,包括不能被拷贝的类、只能在堆上或栈上创建对象的类、不能被继承的类以及单例类。
不能被拷贝的类
在 C++ 中,如果你想设计一个类,使得该类的对象不能被拷贝(即禁止拷贝构造函数和拷贝赋值操作符),你可以将这些函数声明为私有的,或者在 C++11 及更高版本中,可以使用 delete 关键字来显式地禁用它们。以下是一个示例:
C++98 及更低版本的实现:
class NonCopyable {
public:
NonCopyable() {} // 默认构造函数
~NonCopyable() {} // 默认析构函数
private:
NonCopyable(const NonCopyable&); // 声明拷贝构造函数为私有
NonCopyable& operator=(const NonCopyable&); // 声明拷贝赋值操作符为私有
};
C++11 及更高版本的实现:
class NonCopyable {
public:
NonCopyable() = default; // 默认构造函数
~NonCopyable() = default; // 默认析构函数
// 禁用拷贝构造函数和拷贝赋值操作符
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
只能在堆上创建对象的类
如果你想设计一个类,使得该类的对象只能在堆上创建(即禁止在栈上创建对象),你可以将构造函数声明为私有的,并提供一个静态成员函数来创建对象。以下是一个示例:
class HeapOnly {
public:
static HeapOnly* create() {
return new HeapOnly();
}
void destroy() {
delete this;
}
private:
HeapOnly() {} // 私有构造函数
~HeapOnly() {} // 私有析构函数
};
通过将构造函数和析构函数设为私有,用户无法在栈上创建 HeapOnly 类的对象,只能通过 create 静态成员函数在堆上创建对象。
只能在栈上创建对象的类
如果你想设计一个类,使得该类的对象只能在栈上创建(即禁止在堆上创建对象),你可以将 new 操作符重载为私有的。以下是一个示例:
class StackOnly {
public:
StackOnly() {} // 默认构造函数
~StackOnly() {} // 默认析构函数
private:
void* operator new(size_t size); // 重载 new 操作符为私有
void operator delete(void* ptr); // 重载 delete 操作符为私有
};
当然,在 C++11 及更高版本中,你也可以使用 delete 关键字来禁用 new 操作符:
class StackOnly {
public:
StackOnly() = default; // 默认构造函数
~StackOnly() = default; // 默认析构函数
// 禁用 new 操作符
void* operator new(size_t size) = delete;
void operator delete(void* ptr) = delete;
};
通过将 new 操作符设为私有或禁用,用户无法在堆上创建 StackOnly 类的对象,只能在栈上创建对象。
不能被继承的类
如果你想设计一个类,使得该类不能被继承,你可以将构造函数声明为私有的,并提供一个静态成员函数来创建对象。以下是一个示例:
class FinalClass {
public:
static FinalClass* create() {
return new FinalClass();
}
void destroy() {
delete this;
}
private:
FinalClass() {} // 私有构造函数
};
为什么这样可行?当一个类的构造函数是私有的,派生类无法调用基类的构造函数,自然就无法继承该类。
在 C++11 及更高版本中,你也可以使用 final 关键字来声明一个类为最终类,禁止继承:
class FinalClass final {
public:
FinalClass() = default; // 默认构造函数
~FinalClass() = default; // 默认析构函数
};
看起来更加简洁明了。
单例类(重点)
饿汉式单例模式
class Singleton {
public:
static Singleton& getInstance() {
return instance;
}
private:
Singleton() {} // 私有构造函数
~Singleton() {} // 私有析构函数
Singleton(const Singleton&) = delete; // 禁用拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 禁用拷贝赋值操作符
static Singleton instance; // 静态实例
};
Singleton Singleton::instance; // 定义静态实例
这是一个非常简单的饿汉式单例模式实现,不过一般也都是这样创建的,实例在程序启动时就创建好了。
懒汉式单例模式
在 C++11 之前,可以使用双重检查锁定来实现线程安全的懒汉式单例模式:
#include <mutex>
class Singleton {
public:
static Singleton& getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return *instance;
}
private:
Singleton() {} // 私有构造函数
~Singleton() {} // 私有析构函数
Singleton(const Singleton&) = delete; // 禁用拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 禁用拷贝赋值操作符
static Singleton* instance; // 静态实例指针
static std::mutex mtx; // 互斥锁
};
Singleton* Singleton::instance = nullptr; // 初始化静态实例指针
std::mutex Singleton::mtx; // 初始化互斥锁
为什么需要双重检查锁定?
- 第一次检查是为了避免每次调用
getInstance都加锁,只有在实例未创建时才加锁 - 第二次检查是在加锁后再次确认实例是否已经创建,以防止多个线程同时创建实例。
在 C++11 及更高版本中,可以利用局部静态变量的线程安全特性来简化实现:
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 局部静态变量,线程安全
return instance;
}
private:
Singleton() {} // 私有构造函数
~Singleton() {} // 私有析构函数
Singleton(const Singleton&) = delete; // 禁用拷贝构造函数
Singleton& operator=(const Singleton&) = delete; // 禁用拷贝赋值操作符
};
可以看到,代码简单的不止一星半点,而且不需要显式地处理锁的问题,C++11 标准保证了局部静态变量的初始化是线程安全的。
请注意,单例类相比前面几个特殊类,它的应用场景更为广泛,适用于需要全局唯一实例的情况,如配置管理器、日志记录器等。一定要重点掌握单例类的设计与实现。