创建型之单例模式

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

什么是单例模式?

单例模式的使用方法

方案一:局部静态变量(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

=== 程序结束 ===
相关推荐
少控科技7 分钟前
QT第6个程序 - 网页内容摘取
开发语言·qt
darkb1rd8 分钟前
八、PHP SAPI与运行环境差异
开发语言·网络安全·php·webshell
历程里程碑10 分钟前
Linux20 : IO
linux·c语言·开发语言·数据结构·c++·算法
郝学胜-神的一滴12 分钟前
深入浅出:使用Linux系统函数构建高性能TCP服务器
linux·服务器·开发语言·网络·c++·tcp/ip·程序人生
天若有情67313 分钟前
【自研实战】轻量级ASCII字符串加密算法:从设计到落地(防查岗神器版)
网络·c++·算法·安全·数据安全·加密
承渊政道16 分钟前
Linux系统学习【Linux系统的进度条实现、版本控制器git和调试器gdb介绍】
linux·开发语言·笔记·git·学习·gitee
JQLvopkk40 分钟前
C# 轻量级工业温湿度监控系统(含数据库与源码)
开发语言·数据库·c#
玄同7651 小时前
从 0 到 1:用 Python 开发 MCP 工具,让 AI 智能体拥有 “超能力”
开发语言·人工智能·python·agent·ai编程·mcp·trae
czy87874751 小时前
深入了解 C++ 中的 `std::bind` 函数
开发语言·c++
消失的旧时光-19431 小时前
从 Kotlin 到 Dart:为什么 sealed 是处理「多种返回结果」的最佳方式?
android·开发语言·flutter·架构·kotlin·sealed