静态库中单例不唯一的情况

提出问题

A作为静态库,封装了一个单例类,共享库B和共享库C使用A,D作为可执行程序,使用B和C,那么这个单例是否唯一?

Chatgpt的回答如下

如果A是一个共享库,封装了一个单例类,并且B和C都使用了A,那么D作为可执行程序,使用了B和C,这个单例类在整个程序中仍然是唯一的。单例类的唯一性是相对于进程而言的,因此由A封装的单例类在整个程序执行期间只会有一个实例,即使它被不同的共享库使用。

如果A作为静态库,封装了一个单例类,B和C使用A,D作为可执行程序使用B和C。由于静态库在链接时会被整合到可执行程序中,每个使用A的库和可执行程序中都将包含单例类的一个实例。因此,这个单例类在程序中仍然是唯一的,但是这个唯一性是相对于每个包含A的模块而言的,而不是整个程序。每个模块(B、C和D)都会有自己的单例实例。

实验

首先创建一个C++项目,项目结构如下

.
├── CMakeLists.txt
├── MeyerSingleton.cpp
├── MeyerSingleton.h
├── testlib.cpp
├── testlibheader1.cpp
├── testlibheader1.h
├── testlibheader2.cpp
└── testlibheader2.h

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)
set(CMAKE_CXX_STANDARD 17)

project(test)

// libA
add_library(libA SHARED MeyerSingleton.h MeyerSingleton.cpp)

//libB
add_library(libB SHARED testlibheader1.h testlibheader1.cpp)
target_link_libraries(libB libA)

//libC
add_library(libC SHARED testlibheader2.h testlibheader2.cpp)
target_link_libraries(libC libA)

//exeD
add_executable(testABC testlib.cpp)
target_link_libraries(testABC libB libC)
MeyerSingleton.h
#ifndef MEYER_SINGLETON_H
#define MEYER_SINGLETON_H
class MeyerSingleton
{
private:
    MeyerSingleton();
    ~MeyerSingleton();
    MeyerSingleton(const MeyerSingleton&);
    MeyerSingleton& operator=(const MeyerSingleton&);
public:
    static MeyerSingleton& getInstance();
};
#endif

MeyerSingleton.cpp

#include <iostream>
#include "MeyerSingleton.h"

MeyerSingleton &MeyerSingleton::getInstance()
{
    static MeyerSingleton instance;
    return instance;
}

MeyerSingleton::MeyerSingleton(){
    std::cout<<"MeyerSingleton initialized!"<<std::endl;
}
MeyerSingleton::~MeyerSingleton(){
    std::cout<<"MeyerSingleton destroyed!"<<std::endl;
}

testlibheader1和testlibheader2定义了一个函数来调用这个单例,如下
testlibheader1.h

#ifndef TESTLIB_HEADERS_1_H
#define TESTLIB_HEADERS_1_H
namespace tb1{
    void test();
}
#endif
testlibheader1.cpp
#include <iostream>
#include <thread>
#include <sstream>
#include "testlibheader2.h"
#include "MeyerSingleton.h"
namespace tb2{
    void test(){
        std::string msg;
        std::stringstream sin;
        sin << &MeyerSingleton::getInstance();
        sin >> msg;
        std::cout << msg << std::endl;
    }
}

再在可执行程序中使用这俩个库文件

#include "testlibheader1.h"
#include "testlibheader2.h"

int main(int argc, char **argv){
    tb1::test();
    tb2::test();
    return 0;
}

编译运行结果如下:

MeyerSingleton initialized!
0x1051b8000
0x1051b8000
MeyerSingleton destroyed!

运行结果看起来一切正常。
如果把libA作为静态库呢?于是对CMakeLists.txt作出修改

project(test)

// libA
add_library(libA MeyerSingleton.h MeyerSingleton.cpp) //设定libA为静态库

//libB
add_library(libB SHARED testlibheader1.h testlibheader1.cpp)
target_link_libraries(libB libA)

//libC
add_library(libC SHARED testlibheader2.h testlibheader2.cpp)
target_link_libraries(libC libA)

//exeD
add_executable(testABC testlib.cpp)
target_link_libraries(testABC libB libC)

再次编译运行,结果如下:

MeyerSingleton initialized!
0x1027ec000
MeyerSingleton initialized!
0x102800000
MeyerSingleton destroyed!
MeyerSingleton destroyed!

可以看到,libA中的单例类MeyerSingleton初始化了两次,并且通过libB和libC获取到的单例不是同一个。
接下来将libA中MeyerSingleton::getInstance移动到函数内部

#ifndef MEYER_SINGLETON_H
#define MEYER_SINGLETON_H
class MeyerSingleton
{
private:
    MeyerSingleton();
    ~MeyerSingleton();
    MeyerSingleton(const MeyerSingleton&);
    MeyerSingleton& operator=(const MeyerSingleton&);
public:
    static MeyerSingleton& getInstance()
    {
        static MeyerSingleton instance;
        return instance;
    }
};
#endif

再次运行,结果如下

MeyerSingleton initialized!
0x100cac000
0x100cac000
MeyerSingleton destroyed!

看起来问题解决了,再把libA设为动态库后运行结果如下:

MeyerSingleton initialized!
0x10430c000
0x10430c000
MeyerSingleton destroyed!

结论

由此能够得到一个结论,libA作为静态库被libB和libC调用时单例地址可能不一致,经过测试有如下情形:

  • 当libA作为静态库且在cpp文件中实现getInstance时,libB和libC同时作为动态库时出现这个问题
  • 当libA作为动态库时,libB和libC作为动态库或者静态库时都没有这个问题
    所以为了避免这个问题,最好的方式是,将getInstance的实现内联在.h文件中
posted @ 2024-01-27 15:02  料峭春风吹酒醒  阅读(44)  评论(0编辑  收藏  举报