创建型之单例模式

无论是自然中还是程序中,有时候我们只需要某个唯一的事物,例如自然中的太阳,这种时候我们需要一种方法可以任何地方都获得那个全局唯一的对象。

什么是单例模式?

单例模式的使用方法

方案一:局部静态变量(C++11)

cpp 复制代码
class Singleton
{
public:
	static Singleton& getInstance()
	{
		static Singleton singleton;
		return singleton;
	}
private:
	Singleton() = default; //构造函数不允许在外部构造
	~Singleton() = default;
	Singleton(const Singleton&) = delete; //不允许拷贝构造
	Singleton& operator=(const Singleton&) = delete; //不允许赋值函数
};

int main()
{
	auto& singleton = Singleton::getInstance();
}

方案二:使用call_once

cpp 复制代码
class Singleton
{
public:
	Singleton& getInstance()
	{
		std::call_once(init_flag, [](){
			instance.reset(new Singleton());
		});
		return *instance;
	}
private:
	Singleton()=default;
	~Singleton()=default;
	Singleton(const Singleton&)=delete;
	Singleton& operator(const Singleton&)=delete;
private:
	static std::unique_ptr<Singleton> instance;
	static std::once_flag init_flag;
};

单例模式的缺点

单例模式最大的特点就是全局唯一,但是在某种条件下可能会出现不唯一的情况。究其根本原因是动态链接库和可执行文件是单独编译的,所以静态区是独立存在的。

场景一:动态链接库

cpp 复制代码
//---------------singleton.hpp----------------------
#ifndef SINGLETON_H
#define SINGLETON_H

#include <iostream>
#include <string>
#include <memory>

// 不安全的单例实现 - 在头文件中定义
class UnsafeSingleton {
public:
    static UnsafeSingleton& getInstance() {
        static UnsafeSingleton instance;  // 每个包含此文件的模块都有自己的实例
        return instance;
    }
    
    void setValue(int val) { 
        value = val; 
        std::cout << "[" << module_name << "] Set value to: " << val 
                  << " (address: " << this << ")" << std::endl;
    }
    
    int getValue() const { 
        std::cout << "[" << module_name << "] Get value: " << value 
                  << " (address: " << this << ")" << std::endl;
        return value; 
    }
    
    void setModuleName(const std::string& name) {
        module_name = name;
    }
    
    const std::string& getModuleName() const {
        return module_name;
    }
    
private:
    UnsafeSingleton() : value(0), module_name("Unknown") {
        std::cout << "UnsafeSingleton constructed at: " << this << std::endl;
    }
    
    ~UnsafeSingleton() {
        std::cout << "UnsafeSingleton destroyed at: " << this << std::endl;
    }
    
    int value;
    std::string module_name;
    
    // 禁止拷贝
    UnsafeSingleton(const UnsafeSingleton&) = delete;
    UnsafeSingleton& operator=(const UnsafeSingleton&) = delete;
};
//---------------singleton.hpp----------------------

//---------------shared_lib.cpp----------------------
#include "singleton.hpp"
#include <iostream>

// 动态库导出的函数
extern "C" {
    void initialize_library_singleton() {
        std::cout << "=== 动态库: 初始化单例 ===" << std::endl;
        auto& instance = UnsafeSingleton::getInstance();
        instance.setModuleName("SharedLib");
        instance.setValue(100);
    }
    
    void modify_library_singleton(int new_value) {
        std::cout << "=== 动态库: 修改单例 ===" << std::endl;
        auto& instance = UnsafeSingleton::getInstance();
        instance.setValue(new_value);
    }
    
    void print_library_singleton() {
        std::cout << "=== 动态库: 打印单例 ===" << std::endl;
        auto& instance = UnsafeSingleton::getInstance();
        std::cout << "模块: " << instance.getModuleName() 
                  << ", 值: " << instance.getValue() 
                  << ", 地址: " << &instance << std::endl;
    }
    
    // 返回单例地址的函数
    void* get_singleton_address() {
        return &UnsafeSingleton::getInstance();
    }
}

#endif
//---------------shared_lib.cpp----------------------

//---------------main.cpp----------------------
#include "singleton.hpp"
#include <iostream>
#include <dlfcn.h>  // 动态加载库
#include <cassert>

// 声明动态库函数
typedef void (*lib_func_t)();
typedef void (*lib_func_int_t)(int);
typedef void* (*lib_func_addr_t)();

int main() {
    std::cout << "=== 程序启动 ===" << std::endl;
    
    // 第一步:在可执行文件中初始化单例
    std::cout << "\n=== 步骤1: 可执行文件中初始化 ===" << std::endl;
    auto& exe_instance = UnsafeSingleton::getInstance();
    exe_instance.setModuleName("Executable");
    exe_instance.setValue(42);
    
    std::cout << "可执行文件单例地址: " << &exe_instance << std::endl;
    
    // 第二步:动态加载库
    std::cout << "\n=== 步骤2: 加载动态库 ===" << std::endl;
    void* lib_handle = dlopen("./libshared_lib.so", RTLD_LAZY);
    if (!lib_handle) {
        std::cerr << "无法加载动态库: " << dlerror() << std::endl;
        return 1;
    }
    
    // 获取库函数指针
    auto init_func = (lib_func_t)dlsym(lib_handle, "initialize_library_singleton");
    auto modify_func = (lib_func_int_t)dlsym(lib_handle, "modify_library_singleton");
    auto print_func = (lib_func_t)dlsym(lib_handle, "print_library_singleton");
    auto addr_func = (lib_func_addr_t)dlsym(lib_handle, "get_singleton_address");
    
    if (!init_func || !modify_func || !print_func || !addr_func) {
        std::cerr << "无法获取函数符号: " << dlerror() << std::endl;
        dlclose(lib_handle);
        return 1;
    }
    
    // 第三步:在动态库中操作单例
    std::cout << "\n=== 步骤3: 动态库中操作 ===" << std::endl;
    init_func();        // 库中初始化
    modify_func(200);   // 库中修改
    print_func();       // 库中打印
    
    // 第四步:比较地址
    std::cout << "\n=== 步骤4: 比较地址 ===" << std::endl;
    void* lib_singleton_addr = addr_func();
    std::cout << "可执行文件单例地址: " << &exe_instance << std::endl;
    std::cout << "动态库单例地址:     " << lib_singleton_addr << std::endl;
    std::cout << "是否相同? " << (lib_singleton_addr == &exe_instance ? "是" : "否") << std::endl;
    
    // 第五步:验证可执行文件中的值
    std::cout << "\n=== 步骤5: 验证可执行文件中的值 ===" << std::endl;
    std::cout << "可执行文件单例的值: " << exe_instance.getValue() << std::endl;
    std::cout << "可执行文件单例模块: " << exe_instance.getModuleName() << std::endl;
    
    // 第六步:再次修改和验证
    std::cout << "\n=== 步骤6: 再次修改验证 ===" << std::endl;
    std::cout << "在可执行文件中设置值为 300" << std::endl;
    exe_instance.setValue(300);
    
    std::cout << "\n在动态库中查看值:" << std::endl;
    print_func();
    
    // 清理
    dlclose(lib_handle);
    
    std::cout << "\n=== 程序结束 ===" << std::endl;
    return 0;
}
//---------------main.cpp----------------------
bash 复制代码
#---------------CMakeLists.txt----------------------
# === 文件: CMakeLists.txt ===
cmake_minimum_required(VERSION 3.10)
project(singleton_issue)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 创建动态库
add_library(shared_lib SHARED shared_lib.cpp)

# 创建可执行文件
add_executable(main_exe main.cpp)

# 链接动态库
target_link_libraries(main_exe ${CMAKE_DL_LIBS})
#---------------CMakeLists.txt----------------------

#---------------run.sh----------------------
echo "编译项目..."
mkdir -p build
cd build
cmake ..
make

echo -e "\n=== 运行程序 ==="
./main_exe

echo -e "\n=== 查看符号表 ==="
echo "动态库符号:"
nm -C libshared_lib.so | grep UnsafeSingleton

echo -e "\n可执行文件符号:"
nm -C main_exe | grep UnsafeSingleton
#---------------run.sh----------------------

最终运行结果为:

bash 复制代码
=== 程序启动 ===

=== 步骤1: 可执行文件中初始化 ===
UnsafeSingleton constructed at: 0x55a1b2c0c040
[Unknown] Set value to: 42 (address: 0x55a1b2c0c040)
可执行文件单例地址: 0x55a1b2c0c040

=== 步骤2: 加载动态库 ===

=== 步骤3: 动态库中操作 ===
=== 动态库: 初始化单例 ===
UnsafeSingleton constructed at: 0x7f8b3d5a7040
[Unknown] Set value to: 100 (address: 0x7f8b3d5a7040)
=== 动态库: 修改单例 ===
[SharedLib] Set value to: 200 (address: 0x7f8b3d5a7040)
=== 动态库: 打印单例 ===
模块: SharedLib, 值: 200, 地址: 0x7f8b3d5a7040

=== 步骤4: 比较地址 ===
可执行文件单例地址: 0x55a1b2c0c040
动态库单例地址:     0x7f8b3d5a7040
是否相同? 否

=== 步骤5: 验证可执行文件中的值 ===
[Executable] Get value: 42 (address: 0x55a1b2c0c040)
可执行文件单例的值: 42
可执行文件单例模块: Executable

=== 步骤6: 再次修改验证 ===
在可执行文件中设置值为 300
[Executable] Set value to: 300 (address: 0x55a1b2c0c040)

在动态库中查看值:
=== 动态库: 打印单例 ===
模块: SharedLib, 值: 200, 地址: 0x7f8b3d5a7040

=== 程序结束 ===
相关推荐
郝学胜-神的一滴5 小时前
深入解析以太网帧与ARP协议:网络通信的基石
服务器·开发语言·网络·程序人生
王老师青少年编程5 小时前
GESP(C++)考级(七级&八级)真题及详细题解(汇总版)
c++·题解·真题·gesp·csp·七级·八级
lingran__5 小时前
C语言动态内存管理详解
c语言·开发语言
haokan_Jia5 小时前
【java使用LinkedHashMap进行list数据分组写入,顺序并没有按照原始顺序,原因分析】
java·开发语言·list
凯子坚持 c5 小时前
C++大模型SDK开发实录(三):流式交互协议SSE解析与httplib实现原理
开发语言·c++·交互
小屁猪qAq5 小时前
从单例模式说动态链接
c++·单例模式·链接·编译
ghie90905 小时前
基于MATLAB的多旋翼无人机多机编队仿真实现
开发语言·matlab·无人机
少控科技5 小时前
QT新手日记026
开发语言·qt
就是有点傻5 小时前
C#中如何和西门子通信
开发语言·c#