无论是自然中还是程序中,有时候我们只需要某个唯一的事物,例如自然中的太阳,这种时候我们需要一种方法可以任何地方都获得那个全局唯一的对象。
什么是单例模式?
单例模式的使用方法
方案一:局部静态变量(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
=== 程序结束 ===