C++ 特殊类设计

C++ 特殊类设计

十二月 14, 2025 次阅读

在 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 标准保证了局部静态变量的初始化是线程安全的。

请注意,单例类相比前面几个特殊类,它的应用场景更为广泛,适用于需要全局唯一实例的情况,如配置管理器、日志记录器等。一定要重点掌握单例类的设计与实现。