C/C++ 模板与so
在Linux系统中,将相同的模板代码编译到两个不同的共享库(.so
文件)中是一种相对复杂的操作,通常并不推荐,因为这样做可能会导致符号冲突和二进制膨胀。然而,如果你确实有这样的需求,可以通过一些技巧来实现。
假设你有一个C++模板类 MyClass
,并希望将其编译到两个不同的共享库中 libA.so
和 libB.so
。以下是实现这一目标的步骤:
-
创建模板类源代码:
创建一个头文件
MyClass.h
,其中包含模板类的定义:// MyClass.h #ifndef MYCLASS_H #define MYCLASS_Htemplate <typename T> class MyClass { public:MyClass(T value) : value_(value) {}T getValue() const { return value_; }private:T value_; };#endif // MYCLASS_H
-
创建源文件(可选):
通常情况下,模板类的实现是放在头文件中的,因为模板实例化需要在编译时完成。但如果你有模板类的实现需要放在源文件中,你可以这样做:
// MyClass.cpp (可选) #include "MyClass.h"// 如果模板实现复杂,可以放在这里(但通常不推荐)
注意:如果你选择将实现放在源文件中,你需要确保每个使用模板的编译单元(即每个
.cpp
文件)都能看到模板定义,这通常意味着你需要将头文件包含在每个使用模板的.cpp
文件中。 -
创建两个共享库:
为了创建两个不同的共享库,你需要两个独立的构建脚本(如
Makefile
或CMakeLists.txt
)。Makefile 示例:
# Makefile for libA.so CC = g++ CFLAGS = -fPIC -shared TARGET_A = libA.soall:$(CC) $(CFLAGS) -o $(TARGET_A) MyClass.h # 注意:这里通常不需要MyClass.cpp,除非有非模板代码clean:rm -f $(TARGET_A)# Makefile for libB.so # 可以复制上面的Makefile,然后重命名变量 CC = g++ CFLAGS = -fPIC -shared TARGET_B = libB.soall:$(CC) $(CFLAGS) -o $(TARGET_B) MyClass.hclean:rm -f $(TARGET_B)
注意:在创建共享库时,通常不需要
.cpp
文件,因为模板类在头文件中实例化。 -
编译共享库:
使用
make
命令编译两个共享库:make -f Makefile_for_libA # 假设你将第一个Makefile命名为Makefile_for_libA make -f Makefile_for_libB # 假设你将第二个Makefile命名为Makefile_for_libB
-
使用共享库:
在你的应用程序中,你可以链接并使用这两个共享库。但是,你需要确保模板实例化在链接时不会发生冲突。这通常意味着你需要在每个共享库和最终的应用程序中正确地管理模板的实例化。
// main.cpp #include <iostream> #include "MyClass.h"int main() {MyClass<int> objA(42);std::cout << "Value from libA: " << objA.getValue() << std::endl;// 注意:这里我们假设libB中的MyClass实例化和使用是隔离的// 实际情况中,你可能需要更复杂的机制来避免符号冲突// 比如使用命名空间或者不同的模板参数return 0; }
编译和链接你的应用程序:
g++ main.cpp -o myApp -L. -lA -lB -Wl,-rpath,.
-
处理潜在的符号冲突:
如果你发现链接时出现符号冲突,你可能需要使用一些高级技术,如:
- 命名空间:将模板类放在不同的命名空间中。
- 模板参数修改:为两个库中的模板类使用不同的模板参数。
- 编译器特性:利用编译器特性(如GCC的
visibility
属性)来控制符号的导出。
请注意,将相同的模板代码编译到两个不同的共享库中通常是不推荐的,因为这增加了二进制大小和潜在的维护复杂性。如果可能的话,考虑将模板类放在一个共享的、单独的库中,并由其他库和应用程序链接这个共享库。