目录

对象性能_Singleton


对象性能

面向对象很好地解决了“抽象”的问题,但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。 但是某些情况,面向对象所带来的成本必须谨慎处理。

典型模式

  • Singleton
  • Flyweight

1 singleton 单例模式

1.1 模式动机

在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。 如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例? 这应该是类设计者的责任,而不是使用者的责任。

1.2 模式定义

保证一个类仅有一个实例,并提供一个该实例的全局访问点。

1.3 模式示例代码

线程不安全

# include <iostream>
# include <string>

// Singleton类:日志记录器
class Logger {
public:
    // 公共静态方法,用于获取Logger实例
    static Logger* getInstance() {
        if (instance == nullptr) {
            instance = new Logger();
        }
        return instance;
    }

    void log(const std::string& message) {
        std::cout << "日志记录:" << message << std::endl;
    }

private:
    // 私有构造函数,防止外部实例化对象
    Logger() {}

    // 静态成员变量,存储唯一实例
    static Logger* instance;
};

// 初始化静态成员变量
Logger* Logger::instance = nullptr;

int main() {
    // 获取Logger实例
    Logger* logger = Logger::getInstance();

    // 记录日志
    logger->log("这是一条日志信息");

    // 尝试实例化Logger对象(无效,因为构造函数是私有的)
    // Logger* logger2 = new Logger();

    // 释放内存(可选)
    delete logger;

    return 0;
}

线程安全

# include <iostream>
# include <string>
# include <mutex>

// Singleton类:日志记录器
class Logger {
public:
    // 公共静态方法,用于获取Logger实例
    static Logger* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mutex);  // 加锁
            if (instance == nullptr) {
                instance = new Logger();
            }
        }
        return instance;
    }

    void log(const std::string& message) {
        std::cout << "日志记录:" << message << std::endl;
    }

private:
    // 私有构造函数,防止外部实例化对象
    Logger() {}

    // 静态成员变量,存储唯一实例
    static Logger* instance;

    // 静态互斥量,用于线程安全
    static std::mutex mutex;
};

// 初始化静态成员变量
Logger* Logger::instance = nullptr;
std::mutex Logger::mutex;

int main() {
    // 获取Logger实例
    Logger* logger = Logger::getInstance();

    // 记录日志
    logger->log("这是一条日志信息");

    // 释放内存(可选)
    delete logger;

    return 0;
}

双检查锁 (double-checked locking)

# include <iostream>
# include <string>
# include <mutex>

// Singleton类:日志记录器
class Logger {
public:
    // 公共静态方法,用于获取Logger实例
    static Logger* getInstance() {
        // 锁前检查
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mutex);  // 加锁
            // 锁后检查
            if (instance == nullptr) {
                // Todo <2023-07-06, @xcx> : 可能会有 reorder 的问题, order 不确定
                // 1. 分配内存
                // 2. 调用构造函数
                // 3. 赋值给 instance
                // or
                // 1. 分配内存
                // 2. 赋值给 instance
                // 3. 调用构造函数
                instance = new Logger();
            }
        }
        return instance;
    }

    void log(const std::string& message) {
        std::cout << "日志记录:" << message << std::endl;
    }

private:
    // 私有构造函数,防止外部实例化对象
    Logger() {}

    // 静态成员变量,存储唯一实例
    static Logger* instance;

    // 静态互斥量,用于线程安全
    static std::mutex mutex;
};

// 初始化静态成员变量
Logger* Logger::instance = nullptr;
std::mutex Logger::mutex;

int main() {
    // 获取Logger实例
    Logger* logger = Logger::getInstance();

    // 记录日志
    logger->log("这是一条日志信息");

    // 释放内存(可选)
    delete logger;

    return 0;
}

解决 reorder 问题

# include <iostream>
# include <string>
# include <atomic>
# include <mutex>

// Singleton类:日志记录器
class Logger {
public:
    // 公共静态方法,用于获取Logger实例
    static Logger* getInstance() {
        Logger* tmp = instance.load(std::memory_order_acquire);  // 读取实例

        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(mutex);  // 加锁
            tmp = instance.load(std::memory_order_relaxed);  // 重新读取实例
            if (tmp == nullptr) {
                tmp = new Logger();
                instance.store(tmp, std::memory_order_release);  // 存储实例
            }
        }
        return tmp;
    }

    void log(const std::string& message) {
        std::cout << "日志记录:" << message << std::endl;
    }

private:
    // 私有构造函数,防止外部实例化对象
    Logger() {}

    // 静态原子指针,存储唯一实例
    static std::atomic<Logger*> instance;

    // 静态互斥量,用于线程安全
    static std::mutex mutex;
};

// 初始化静态原子指针
std::atomic<Logger*> Logger::instance(nullptr);
std::mutex Logger::mutex;

int main() {
    // 获取Logger实例
    Logger* logger = Logger::getInstance();

    // 记录日志
    logger->log("这是一条日志信息");

    // 释放内存(可选)
    delete logger;

    return 0;
}

2 要点总结

Singleton 模式中的实例构造器可以设置为 protected 以允许子类派生。

Singleton 模式一般不要支持拷贝构造函数和 Clone 接口,因为这有可能导致多个对象实例,与 Singleton 模式的初衷违背。

如何实现多线程环境下安全的 Singleton?注意对双检查锁的正确实现。