设计模式:C++ 单例模式 (Singleton in C++)

设计模式:C++ 单例模式 {Singleton in C++}

  • [1. Naïve Singleton](#1. Naïve Singleton)
  • [2. Thread-safe Singleton (线程安全单例模式)](#2. Thread-safe Singleton (线程安全单例模式))
  • [3. MNN Singleton](#3. MNN Singleton)
    • [3.1. `source\backend\opencl\core\OpenCLBackend.cpp`](#3.1. source\backend\opencl\core\OpenCLBackend.cpp)
    • [3.2. `source\backend\cuda\core\CUDABackend.cpp`](#3.2. source\backend\cuda\core\CUDABackend.cpp)
    • [3.3. `source\backend\opengl\GLBackend.cpp`](#3.3. source\backend\opengl\GLBackend.cpp)
    • [3.4. `source\backend\tensorrt\backend\TRTBackend.cpp`](#3.4. source\backend\tensorrt\backend\TRTBackend.cpp)
    • [3.5. `source\backend\metal\MetalBackend.mm`](#3.5. source\backend\metal\MetalBackend.mm)
    • [3.6. `source\backend\hiai\backend\NPUBackend.cpp`](#3.6. source\backend\hiai\backend\NPUBackend.cpp)
    • [3.7. 为什么只 `new` 不 `delete`?](#3.7. 为什么只 newdelete?)
    • [3.8. MNN C++ 单例模式](#3.8. MNN C++ 单例模式)
  • References

C++ 单例模式
https://refactoringguru.cn/design-patterns/singleton/cpp/example
https://refactoringguru.cn/design-patterns/singleton

Singleton in C++
https://refactoring.guru/design-patterns/singleton/cpp/example
https://refactoring.guru/design-patterns/singleton

Singleton is a creational design pattern, which ensures that only one object of its kind exists and provides a single point of access to it for any other code.

单例是一种创建型设计模式,让你能够保证一个类只有一个实例,并提供一个访问该实例的全局节点。

Singleton has almost the same pros and cons as global variables. Although they're super-handy, they break the modularity of your code.

单例拥有与全局变量相同的优缺点。尽管它们非常有用,但却会破坏代码的模块化特性。

为什么会有人想要控制一个类所拥有的实例数量?

The most common reason for this is to control access to some shared resource - for example, a database or a file. Imagine that you created an object, but after a while decided to create a new one. Instead of receiving a fresh object, you'll get the one you already created.

最常见的原因是控制某些共享资源 (例如数据库或文件) 的访问权限。如果你创建了一个对象,同时过一会儿后你决定再创建一个新对象,此时你会获得之前已创建的对象,而不是一个新对象。

All implementations of the Singleton have these two steps in common:

  • Make the default constructor private, to prevent other objects from using the new operator with the Singleton class.
    将默认构造函数设为私有,防止其他对象使用单例类的 new 运算符。
  • Create a static creation method that acts as a constructor. Under the hood, this method calls the private constructor to create an object and saves it in a static field. All following calls to this method return the cached object.
    新建一个静态构建方法作为构造函数。该函数会 "偷偷" 调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象。

If your code has access to the Singleton class, then it's able to call the Singleton's static method. So whenever that method is called, the same object is always returned.

如果你的代码能够访问单例类,那它就能调用单例类的静态方法。无论何时调用该方法,它总是会返回相同的对象。

Singleton can be recognized by a static creation method, which returns the same cached object.

单例可以通过返回相同缓存对象的静态构建方法来识别。

复制代码
singleton /ˈsɪŋɡltən/
n. (所提及的) 单项物,单个的人;单身男子 (或女子);(非孪生的) 单生儿,单生幼畜

Make the constructor of the class private. The static method of the class will still be able to call the constructor, but not the other objects.

将类的构造函数设为私有。类的静态方法仍能调用构造函数,但是其他对象不能调用。

1. Naïve Singleton

复制代码
#include <iostream>
#include <thread>
#include <string>

/*
 * The Singleton class defines the `GetInstance` method that serves as an
 * alternative to constructor and lets clients access the same instance of this class over and over.
 */
class Singleton {

private:
    /*
    * The Singleton's constructor/destructor should always be private to
    * prevent direct construction/desctruction calls with the `new`/`delete` operator.
    */
    explicit Singleton(std::string value) : value_(std::move(value)) {
        std::cout << "Construction: " << value_ << "\n";
    }

    ~Singleton() = default;

    static Singleton* instance_ptr_;

    std::string value_;

public:
    /*
     * Singletons should not be cloneable.
     */
    Singleton(const Singleton&) = delete;

    /*
     * Singletons should not be assignable.
     */
    Singleton& operator=(const Singleton&) = delete;

    /*
     * This is the static method that controls the access to the singleton instance.
     * On the first run, it creates a singleton object and places it into the static field.
     * On subsequent runs, it returns the client existing object stored in the static field.
     */
    static Singleton* GetInstance(const std::string& value);

    /*
     * Finally, any singleton should define some business logic, which can be executed on its instance.
     */
    void BusinessLogic() {
    }

    std::string GetValue() const {
        return value_;
    }
};

/**
* Static members initialization.
*/
Singleton* Singleton::instance_ptr_{ nullptr };

/*
 * The first time we call GetInstance we will lock the storage location
 * and then we make sure again that the variable is null and then we set the value.
 */
Singleton *Singleton::GetInstance(const std::string& value) {
    /*
    * instance = new Singleton is dangeruous in case two instance threads wants to access at the same time.
    */

    if (instance_ptr_ == nullptr) {
        instance_ptr_ = new Singleton(value);
    }
    return instance_ptr_;
}

void ThreadTask(int id, const std::string& name) {
    // Following code emulates slow initialization.
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance(name);
    std::cout << "Thread [" << id << "] sees value: " << singleton->GetValue() << "\n";
}

int main() {
    std::cout << "If you see the same value, then singleton was reused.\n" <<
        "If you see different values, then 2 singletons were created.\n\n";

    std::thread t1(ThreadTask, 1, "Yong");
    std::thread t2(ThreadTask, 2, "Qiang");

    t1.join();
    t2.join();

    return 0;
}

在多线程环境下,两个线程可能同时通过 if (instance_ptr_ == nullptr) 的检查,导致重复创建对象和内存泄漏。

复制代码
If you see the same value, then singleton was reused.
If you see different values, then 2 singletons were created.

Construction: Yong
Construction: Qiang
Thread [1] sees value: Yong
Thread [2] sees value: Qiang
请按任意键继续. . .

2. Thread-safe Singleton (线程安全单例模式)

复制代码
#include <iostream>
#include <thread>
#include <string>
#include <mutex>

/*
 * The Singleton class defines the `GetInstance` method that serves as an
 * alternative to constructor and lets clients access the same instance of this class over and over.
 */
class Singleton {

private:
    /*
    * The Singleton's constructor/destructor should always be private to
    * prevent direct construction/desctruction calls with the `new`/`delete` operator.
    */
    explicit Singleton(std::string value) : value_(std::move(value)) {
        std::cout << "Construction: " << value_ << "\n";
    }

    ~Singleton() = default;

    static Singleton* instance_ptr_;
    static std::mutex mutex_;

    std::string value_;

public:
    /*
     * Singletons should not be cloneable.
     */
    Singleton(const Singleton&) = delete;

    /*
     * Singletons should not be assignable.
     */
    Singleton& operator=(const Singleton&) = delete;

    /*
     * This is the static method that controls the access to the singleton instance.
     * On the first run, it creates a singleton object and places it into the static field.
     * On subsequent runs, it returns the client existing object stored in the static field.
     */
    static Singleton* GetInstance(const std::string& value);

    /*
     * Finally, any singleton should define some business logic, which can be executed on its instance.
     */
    void BusinessLogic() {
    }

    std::string GetValue() const {
        return value_;
    }
};

/**
* Static members initialization.
*/
Singleton* Singleton::instance_ptr_{ nullptr };
std::mutex Singleton::mutex_;

/*
 * The first time we call GetInstance we will lock the storage location
 * and then we make sure again that the variable is null and then we set the value.
 */
Singleton *Singleton::GetInstance(const std::string& value) {
    std::lock_guard<std::mutex> lock(mutex_);

    if (instance_ptr_ == nullptr) {
        instance_ptr_ = new Singleton(value);
    }
    return instance_ptr_;
}

void ThreadTask(int id, const std::string& name) {
    // Following code emulates slow initialization.
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    Singleton* singleton = Singleton::GetInstance(name);
    std::cout << "Thread [" << id << "] sees value: " << singleton->GetValue() << "\n";
}

int main() {
    std::cout << "If you see the same value, then singleton was reused.\n" <<
        "If you see different values, then 2 singletons were created.\n\n";

    std::thread t1(ThreadTask, 1, "Yong");
    std::thread t2(ThreadTask, 2, "Qiang");

    t1.join();
    t2.join();

    return 0;
}

explicit 关键字防止构造函数隐式转换,构造函数使用 explicit 防止隐式转换。在构造函数中使用 std::move 处理字符串,减少一次拷贝开销。

GetValue() 标记为 const,这是良好的 C++ 编程习惯,确保该函数不会修改对象状态。

当前代码每次调用 GetInstance() 都会加锁,性能开销较大。

复制代码
If you see the same value, then singleton was reused.
If you see different values, then 2 singletons were created.

Construction: Yong
Thread [1] sees value: Yong
Thread [2] sees value: Yong
请按任意键继续. . .

3. MNN Singleton

3.1. source\backend\opencl\core\OpenCLBackend.cpp

复制代码
enum GpuMemObject { AUTO = 0, BUFFER = 1, IMAGE = 2};


std::map<std::pair<OpType, GpuMemObject>, OpenCLBackend::Creator*>* gCreator() {
    static std::once_flag once;
    static std::map<std::pair<OpType, GpuMemObject>, OpenCLBackend::Creator*>* creators = nullptr;
    std::call_once(once, [&]() { creators = new std::map<std::pair<OpType, GpuMemObject>, OpenCLBackend::Creator*>; });
    return creators;
};


static bool addCreator(std::pair<OpType, GpuMemObject> t, Creator *c);
bool OpenCLBackend::addCreator(std::pair<OpType, GpuMemObject> t, Creator* c) {
    auto map = gCreator();
    if (map->find(t) != map->end()) {
        MNN_PRINT("Error: %d type, %d GpuMemObject has be added\n", t.first, t.second);
        return false;
    }
    map->insert(std::make_pair(t, c));
    return true;
}

3.2. source\backend\cuda\core\CUDABackend.cpp

复制代码
std::map<OpType, CUDABackend::Creator*>* gCreator() {
    static std::map<OpType, CUDABackend::Creator*>* creators = nullptr;
    static std::once_flag gOnce;
    std::call_once(gOnce, [&]() { creators = new std::map<OpType, CUDABackend::Creator*>; });
    return creators;
};


static bool addCreator(OpType t, Creator *c);
bool CUDABackend::addCreator(OpType t, Creator* c) {
    auto map = gCreator();
    if (map->find(t) != map->end()) {
        MNN_PRINT("Error: %d type has be added\n", t);
        return false;
    }
    map->insert(std::make_pair(t, c));
    return true;
}

3.3. source\backend\opengl\GLBackend.cpp

复制代码
std::map<OpType, GLBackend::Creator*>* gCreator() {
    static std::once_flag once;
    static std::map<OpType, GLBackend::Creator*>* creators = nullptr;
    std::call_once(once, [&]() { creators = new std::map<OpType, GLBackend::Creator*>; });
    return creators;
};


static bool addCreator(OpType t, Creator *c);
bool GLBackend::addCreator(OpType t, Creator* c) {
    auto map = gCreator();
    if (map->find(t) != map->end()) {
        MNN_PRINT("Error: %d type has be added\n", t);
        return false;
    }
    map->insert(std::make_pair(t, c));
    return true;
}

3.4. source\backend\tensorrt\backend\TRTBackend.cpp

复制代码
std::map<OpType, TRTBackend::Creator*>* gCreator() {
    static std::once_flag once;
    static std::map<OpType, TRTBackend::Creator*>* creators = nullptr;
    std::call_once(once, [&]() { creators = new std::map<OpType, TRTBackend::Creator*>; });
    return creators;
};


static bool addCreator(OpType t, Creator* c);
bool TRTBackend::addCreator(OpType t, Creator* c) {
    auto map = gCreator();
    if (map->find(t) != map->end()) {
        MNN_PRINT("Error: %d type has be added\n", t);
        return false;
    }
    map->insert(std::make_pair(t, c));
    return true;
}

3.5. source\backend\metal\MetalBackend.mm

复制代码
static inline std::map<OpType, MetalBackend::Creator *> *getCreatorMap() {
    static std::once_flag of;
    static std::map<OpType, MetalBackend::Creator *> *ret = nullptr;
    std::call_once(of, [&]() { ret = new std::map<OpType, MetalBackend::Creator *>; });
    return ret;
}


static void addCreator(OpType type, Creator *creator);
void MetalBackend::addCreator(OpType t, Creator *c) {
    auto map = getCreatorMap();
    if (map->find(t) != map->end()) {
        MNN_PRINT("Error: %d type has be added\n", t);
    }
    map->insert(std::make_pair(t, c));
}

3.6. source\backend\hiai\backend\NPUBackend.cpp

复制代码
    static inline std::map<OpType, NPUBackend::Creator*>* getCreatorMap() {
        static std::once_flag of;
        static std::map<OpType, NPUBackend::Creator*>* ret = nullptr;
        std::call_once(of, [&]() { ret = new std::map<OpType, NPUBackend::Creator*>; });
        return ret;
    }


    static bool addCreator(OpType t, Creator* c);
    bool NPUBackend::addCreator(OpType t, Creator* c) {
        auto map = getCreatorMap();
        if (map->find(t) != map->end()) {
            MNN_PRINT("Error: %d type has be added\n", t);
            return false;
        }
        map->insert(std::make_pair(t, c));
        return true;
    }

3.7. 为什么只 newdelete?

在同一个编译单元 (源文件) 内,C++ 全局对象、静态对象的构造与销毁顺序是确定的,但在不同编译单元之间是未定义的。

如果 creators 是一个普通的静态变量 (非指针),它会在程序退出阶段 (main 函数结束以后) 被析构。如果此时还有其他后台线程或全局对象在运行,并且其析构函数尝试调用 gCreator(),程序就会访问一个已销毁的对象,导致 Segment Fault。通过 new 将对象分配在堆上,并由静态指针指向它。由于没有显式 delete,这个 std::map 对象的生命周期将延续到进程彻底结束,确保了在程序退出的任何阶段访问它都是安全的。

通过 new 分配在堆上且不销毁,本质上是将这个对象的生命周期"延长"到了与进程的物理寿命完全一致,从而彻底规避了因销毁顺序不确定导致的"临终崩溃"。

手动 delete 一个 std::map 需要遍历整棵树并逐个释放节点内存,这会消耗 CPU 时间。当进程结束时,操作系统会直接回收该进程分配的所有物理内存页。这种"由 OS 统一回收"的速度远快于程序内部手动析构。即使你的 C++ 程序中有成千上万个 new 出来的对象没写 delete,只要进程一关闭,系统就会执行"清场"操作。

如果使用智能指针,程序退出时它会尝试"负责任"地销毁 std::map。一旦销毁,就可能撞上"跨编译单元析构顺序"的枪口,导致程序在退出瞬间崩溃。

在 C++ 中,任何通过 new 申请的内存如果没有对应的 delete,在程序运行期间这块内存都不会被回收。一旦你执行了 new,编译器就撒手不管了。它会在内存管理表中标记这块区域"已占用"。只要你不调用 delete,这块内存就被判定为正在使用中。

**内存泄漏通常指在循环中不断申请但不释放。而这里的 creators 是通过 std::once_flag 初始化的,全局只会分配一次。**即便多个线程同时第一次调用 gCreator(),也只会有一个线程创建 std::map 对象,避免了多线程竞态导致的多次分配。creators 指针存储在静态区,保证了全局唯一性。

根据 C++11 标准,并发执行到静态局部变量声明处时,初始化会被同步,无需手动加锁。静态局部变量在程序结束时会自动调用析构函数,避免了内存泄漏的风险。

3.8. MNN C++ 单例模式

复制代码
#include <iostream>
#include <map>
#include <vector>
#include <thread>
#include <mutex>
#include <string>

enum GpuMemObject { AUTO = 0, BUFFER = 1, IMAGE = 2 };

enum OpType {
    OpType_ArgMax = 0,
    OpType_BinaryOp = 1,
    OpType_Cast = 2,
    OpType_GroupNorm = 3,
    OpType_InstanceNorm = 4,
    OpType_MatMul = 5,
    OpType_LayerNorm = 6,
    OpType_GridSample = 7,
    OpType_MIN = OpType_ArgMax,
    OpType_MAX = OpType_GridSample
};

class Creator {
public:
    virtual ~Creator() = default;
    virtual void onCreate() const = 0;
};

class GridSampleBufCreator :public Creator {
public:
    virtual ~GridSampleBufCreator() = default;
    virtual void onCreate() const override {
        printf("class GridSampleBufCreator\n");
    }
};

class LayerNormBufCreator : public Creator {
public:
    virtual ~LayerNormBufCreator() = default;
    virtual void onCreate() const override {
        printf("class LayerNormBufCreator\n");
    }
};

class GroupNormBufCreator : public Creator {
public:
    virtual ~GroupNormBufCreator() = default;
    virtual void onCreate() const override {
        printf("class GroupNormBufCreator\n");
    }
};

std::map<std::pair<OpType, GpuMemObject>, Creator*>* gCreator() {
    printf("std::map<std::pair<OpType, GpuMemObject>, Creator*>* gCreator()\n");
    static std::once_flag once;
    static std::map<std::pair<OpType, GpuMemObject>, Creator*>* creators = nullptr;
    std::call_once(once, [&]() { creators = new std::map<std::pair<OpType, GpuMemObject>, Creator*>; printf("class GroupNormBufCreator\n"); });
    return creators;
};

bool addCreator(std::pair<OpType, GpuMemObject> t, Creator* c) {
    auto map = gCreator();
    if (map->find(t) != map->end()) {
        printf("Error: %d type, %d GpuMemObject has be added\n", t.first, t.second);
        return false;
    }
    map->insert(std::make_pair(t, c));
    return true;
}

void ___OpenCLGridSampleBufCreator__OpType_GridSample__BUFFER__() {
    static GridSampleBufCreator _temp;
    addCreator(std::make_pair(OpType_GridSample, BUFFER), &_temp);
}

void ___OpenCLLayerNormBufCreator__OpType_LayerNorm__BUFFER__() {
    static LayerNormBufCreator _temp;
    addCreator(std::make_pair(OpType_LayerNorm, BUFFER), &_temp);
}

void ___OpenCLGroupNormBufCreator__OpType_GroupNorm__BUFFER__() {
    static GroupNormBufCreator _temp;
    addCreator(std::make_pair(OpType_GroupNorm, BUFFER), &_temp);
}

int main() {
    ___OpenCLGridSampleBufCreator__OpType_GridSample__BUFFER__();
    ___OpenCLLayerNormBufCreator__OpType_LayerNorm__BUFFER__();
    ___OpenCLGroupNormBufCreator__OpType_GroupNorm__BUFFER__();

    return 0;
}

std::map<std::pair<OpType, GpuMemObject>, Creator*>* gCreator()
class GroupNormBufCreator
std::map<std::pair<OpType, GpuMemObject>, Creator*>* gCreator()
std::map<std::pair<OpType, GpuMemObject>, Creator*>* gCreator()
请按任意键继续. . .

References

1\] Yongqiang Cheng (程永强), \[2\] `Refactoring.Guru`, \[3\] `Refactoring.Guru`,

相关推荐
得一录2 小时前
AI Agent的主流设计模式之反射模式
人工智能·设计模式
我爱cope2 小时前
【从0开始学设计模式-1| 设计模式简介、UML图】
设计模式·uml
※DX3906※3 小时前
Java多线程3--设计模式,线程池,定时器
java·开发语言·ide·设计模式·intellij idea
J_liaty13 小时前
23种设计模式一中介者模式
设计模式·中介者模式
郝学胜-神的一滴1 天前
在Vibe Coding时代,学习设计模式与软件架构
人工智能·学习·设计模式·架构·软件工程
九狼1 天前
Flutter SSE 流式响用 Dio 实现 OpenAI 兼容接口的逐 Token 输出
http·设计模式·api
郝学胜-神的一滴1 天前
单例模式:从经典实现到Vibe Coding时代的思考
开发语言·c++·程序人生·单例模式·设计模式·多线程
J_liaty2 天前
Java设计模式全解析:23种模式的理论与实践指南
java·设计模式
资深web全栈开发2 天前
设计模式之观察者模式 (Observer Pattern)
观察者模式·设计模式