跨编译单元的单例:链接库与链接 objects
Singleton across compilation units: linking library vs linking objects
如果标题不完整,我深表歉意self-explanatory。我试图理解为什么我的单例工厂模式无法正常工作,并且我 运行 在使用库与链接单个 object 文件时遇到了奇怪的差异。
这是代码的简化版本:
main.cpp
#include <iostream>
#include "bar.hpp"
int main (int /*argc*/, char** /*argv*/)
{
A::get().print();
return 0;
}
bar.hpp
#ifndef BAR_HPP
#define BAR_HPP
#include <iostream>
class A
{
public:
static A& get ()
{
static A a;
return a;
}
bool set(const int i)
{
m_i = i;
print();
return true;
}
void print ()
{
std::cout << "print: " << m_i << "\n";
}
private:
int m_i;
A () : m_i(0) {}
};
#endif // BAR_HPP
baz.hpp
#ifndef BAZ_HPP
#define BAZ_HPP
#include "bar.hpp"
namespace
{
static bool check = A::get().set(2);
}
#endif // BAZ_HPP
baz.cpp
#include "baz.hpp"
现在,我用两种方式构建 "project":
生成文件:
all:
g++ -std=c++11 -c baz.cpp
g++ -std=c++11 -o test main.cpp baz.o
lib:
g++ -std=c++11 -c baz.cpp
ar rvs mylib.a baz.o
g++ -std=c++11 -o test main.cpp mylib.a
这是我得到的输出:
$ make all
$ ./test
print: 2
print: 2
$ make lib
$ ./test
print: 0
在第一种情况下,在 baz.hpp 中调用 A::get().set(2)
,然后在主函数中使用相同的 A 实例化,因此打印 2
。在第二种情况下,baz.hpp 中对 A::get().set(2)
的调用永远不会发生,并且在 main 函数中打印构造函数设置的值(即 0
)。
所以我终于可以问我的问题了:为什么两种情况下的行为不同?我希望要么都打印 0 一次,要么打印 2 两次。我一直认为库只是传送 object 文件的一种紧凑方式,并且链接 mylib.a 的行为应该与直接链接 baz.o 的行为相同。为什么不是这样?
编辑:正如 Richard 所解释的,原因是 baz.cpp 中定义的符号在 main.cpp 中不需要,因此 baz.o 没有从库中提取和链接。这就引出了另一个问题:是否有变通方法来确保指令A::get().set(2)
被执行?我想避免将单例设为全局 object,但我不确定是否可行。我还想避免在 main 中包含 baz.hpp,因为可能有很多 bazxyz.hpp
并且需要 main.cpp 提前知道所有这些,违背了整个目的factory-like 注册过程...
如果这是一个静态库,那么某个地方的某个模块将不得不在将要向工厂注册的对象的每个实现文件中解决一些问题。
一个合理的位置是 bar.cpp
(这是您还没有的文件)。它将包含 A
的部分或全部实现以及一些调用您将要创建的小部件的注册函数的方法。
自我发现仅在目标文件链接到可执行文件时才有效。这使 c++ 启动序列有机会了解和构造具有全局链接的所有对象。
如果标题不完整,我深表歉意self-explanatory。我试图理解为什么我的单例工厂模式无法正常工作,并且我 运行 在使用库与链接单个 object 文件时遇到了奇怪的差异。
这是代码的简化版本:
main.cpp
#include <iostream>
#include "bar.hpp"
int main (int /*argc*/, char** /*argv*/)
{
A::get().print();
return 0;
}
bar.hpp
#ifndef BAR_HPP
#define BAR_HPP
#include <iostream>
class A
{
public:
static A& get ()
{
static A a;
return a;
}
bool set(const int i)
{
m_i = i;
print();
return true;
}
void print ()
{
std::cout << "print: " << m_i << "\n";
}
private:
int m_i;
A () : m_i(0) {}
};
#endif // BAR_HPP
baz.hpp
#ifndef BAZ_HPP
#define BAZ_HPP
#include "bar.hpp"
namespace
{
static bool check = A::get().set(2);
}
#endif // BAZ_HPP
baz.cpp
#include "baz.hpp"
现在,我用两种方式构建 "project":
生成文件:
all:
g++ -std=c++11 -c baz.cpp
g++ -std=c++11 -o test main.cpp baz.o
lib:
g++ -std=c++11 -c baz.cpp
ar rvs mylib.a baz.o
g++ -std=c++11 -o test main.cpp mylib.a
这是我得到的输出:
$ make all
$ ./test
print: 2
print: 2
$ make lib
$ ./test
print: 0
在第一种情况下,在 baz.hpp 中调用 A::get().set(2)
,然后在主函数中使用相同的 A 实例化,因此打印 2
。在第二种情况下,baz.hpp 中对 A::get().set(2)
的调用永远不会发生,并且在 main 函数中打印构造函数设置的值(即 0
)。
所以我终于可以问我的问题了:为什么两种情况下的行为不同?我希望要么都打印 0 一次,要么打印 2 两次。我一直认为库只是传送 object 文件的一种紧凑方式,并且链接 mylib.a 的行为应该与直接链接 baz.o 的行为相同。为什么不是这样?
编辑:正如 Richard 所解释的,原因是 baz.cpp 中定义的符号在 main.cpp 中不需要,因此 baz.o 没有从库中提取和链接。这就引出了另一个问题:是否有变通方法来确保指令A::get().set(2)
被执行?我想避免将单例设为全局 object,但我不确定是否可行。我还想避免在 main 中包含 baz.hpp,因为可能有很多 bazxyz.hpp
并且需要 main.cpp 提前知道所有这些,违背了整个目的factory-like 注册过程...
如果这是一个静态库,那么某个地方的某个模块将不得不在将要向工厂注册的对象的每个实现文件中解决一些问题。
一个合理的位置是 bar.cpp
(这是您还没有的文件)。它将包含 A
的部分或全部实现以及一些调用您将要创建的小部件的注册函数的方法。
自我发现仅在目标文件链接到可执行文件时才有效。这使 c++ 启动序列有机会了解和构造具有全局链接的所有对象。