由于模板函数的实现需要在编译时实例化,传统的动态库设计需要特殊处理。以下是一个完整的设计范例:
目录结构
my_library/
├── include/
│ ├── my_library.h # 公共API头文件
│ └── my_library_impl.h # 实现细节(可选)
├── src/
│ ├── explicit_instantiations.cpp # 显式实例化
│ ├── private_impl.hpp # 私有实现
│ └── build.py # 构建脚本
└── examples/
├── use_static.cpp # 静态链接示例
└── use_dynamic.cpp # 动态链接示例
1. 公共API头文件设计
include/my_library.h
cpp
#pragma once
// 前置声明
namespace my_library {
// 版本信息
constexpr int VERSION_MAJOR = 1;
constexpr int VERSION_MINOR = 0;
constexpr int VERSION_PATCH = 0;
// 模板函数声明
template<typename T>
T add(T a, T b);
template<typename T>
T max(T a, T b);
template<typename Container>
typename Container::value_type sum(const Container& container);
// 非模板函数可以正常导出
void initialize();
void cleanup();
// 显式实例化的模板 - 通过extern声明
extern template int add<int>(int, int);
extern template double add<double>(double, double);
extern template float max<float>(float, float);
extern template double max<double>(double, double);
}
也可以考虑加 extern "C" { ... }
2. 实现文件
src/private_impl.hpp (内部头文件)
cpp
#pragma once
// 这是内部实现文件,不暴露给用户
namespace my_library::detail {
template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;
// 内部实现细节
template<Arithmetic T>
inline T internal_add(T a, T b) {
return a + b;
}
}
src/explicit_instantiations.cpp
cpp
#include "my_library.h"
#include <vector>
#include <list>
// 包含模板实现
namespace my_library {
// 模板函数定义
template<typename T>
T add(T a, T b) {
// 可以调用内部实现
return detail::internal_add(a, b);
}
template<typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
template<typename Container>
typename Container::value_type sum(const Container& container) {
using ValueType = typename Container::value_type;
ValueType total = ValueType();
for (const auto& item : container) {
total += item;
}
return total;
}
// 非模板函数实现
void initialize() {
// 初始化逻辑
}
void cleanup() {
// 清理逻辑
}
}
// ========== 显式实例化 ==========
// 这些实例会被编译进动态库
// 基本类型实例化
template int my_library::add<int>(int, int);
template double my_library::add<double>(double, double);
template float my_library::add<float>(float, float);
template int my_library::max<int>(int, int);
template double my_library::max<double>(double, double);
template float my_library::max<float>(float, float);
// 容器实例化(需要常用类型)
template int my_library::sum<std::vector<int>>(const std::vector<int>&);
template double my_library::sum<std::vector<double>>(const std::vector<double>&);
template float my_library::sum<std::list<float>>(const std::list<float>&);
// 如果有外部链接要求,需要导出符号
#ifdef _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __attribute__((visibility("default")))
#endif
// 显式实例化的模板需要导出
extern "C" {
EXPORT int my_library_add_int(int a, int b) {
return my_library::add(a, b);
}
EXPORT double my_library_add_double(double a, double b) {
return my_library::add(a, b);
}
}
3. 构建脚本
src/build.py
python
#!/usr/bin/env python3
import subprocess
import sys
import os
def build_library():
"""构建动态库,包含显式实例化"""
# 编译器检测
compiler = "g++" if sys.platform != "win32" else "cl"
# 编译选项
common_flags = "-std=c++20 -O2 -I../include"
if sys.platform == "darwin":
# macOS
cmd = f"{compiler} {common_flags} -dynamiclib "
cmd += "-o ../lib/libmy_library.dylib "
cmd += "explicit_instantiations.cpp"
elif sys.platform == "win32":
# Windows
cmd = f"{compiler} {common_flags} /LD "
cmd += "/Fe../lib/my_library.dll "
cmd += "explicit_instantiations.cpp"
else:
# Linux
cmd = f"{compiler} {common_flags} -fPIC -shared "
cmd += "-o ../lib/libmy_library.so "
cmd += "explicit_instantiations.cpp"
print(f"执行: {cmd}")
subprocess.run(cmd, shell=True, check=True)
if __name__ == "__main__":
# 创建输出目录
os.makedirs("../lib", exist_ok=True)
build_library()
4. 使用示例
examples/use_static.cpp (静态链接方式)
cpp
// 当用户需要额外的模板实例化时,可以包含完整实现
#define MY_LIBRARY_HEADER_ONLY
#include "my_library.h"
// 用户自定义实例化
template<typename T>
T my_library::add(T a, T b) {
return a + b;
}
int main() {
// 使用预定义的实例
auto result1 = my_library::add(5, 3); // 使用动态库中的实例
// 用户自定义类型
struct Point {
double x, y;
Point operator+(const Point& other) const {
return {x + other.x, y + other.y};
}
};
Point p1{1.0, 2.0};
Point p2{3.0, 4.0};
// 这会触发即时编译,因为Point不是预实例化的
// auto result2 = my_library::add(p1, p2); // 需要取消注释模板定义
return 0;
}
examples/use_dynamic.cpp (动态链接方式)
cpp
#include "my_library.h"
#include <iostream>
#include <vector>
int main() {
my_library::initialize();
// 使用预实例化的模板
int int_result = my_library::add(10, 20);
double double_result = my_library::add(10.5, 20.3);
std::cout << "int add: " << int_result << std::endl;
std::cout << "double add: " << double_result << std::endl;
// 使用容器
std::vector<int> numbers = {1, 2, 3, 4, 5};
int sum_result = my_library::sum(numbers);
std::cout << "sum: " << sum_result << std::endl;
my_library::cleanup();
return 0;
}
5. CMake 构建配置
CMakeLists.txt
cpp
cmake_minimum_required(VERSION 3.15)
project(MyTemplateLibrary VERSION 1.0.0)
# 设置C++标准
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 创建库
add_library(my_library SHARED
src/explicit_instantiations.cpp
)
# 包含目录
target_include_directories(my_library
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
src
)
# 安装配置
install(TARGETS my_library
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
)
install(DIRECTORY include/ DESTINATION include)
# 可选:头文件模式(当用户需要自定义实例化时)
add_library(my_library_header_only INTERFACE)
target_include_directories(my_library_header_only
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)
# 示例程序
add_executable(example_dynamic examples/use_dynamic.cpp)
target_link_libraries(example_dynamic my_library)
add_executable(example_static examples/use_static.cpp)
target_link_libraries(example_static my_library_header_only)
6. 替代方案:类型擦除接口
如果模板太复杂,可以使用类型擦除:
cpp
// include/my_library_erased.h
#pragma once
#include <memory>
#include <functional>
namespace my_library {
class Calculator {
public:
template<typename T>
Calculator(T operation) :
self_(std::make_shared<Model<T>>(std::move(operation))) {}
double calculate(double a, double b) {
return self_->calculate(a, b);
}
private:
struct Concept {
virtual ~Concept() = default;
virtual double calculate(double, double) = 0;
};
template<typename T>
struct Model : Concept {
T operation_;
Model(T op) : operation_(std::move(op)) {}
double calculate(double a, double b) override {
return operation_(a, b);
}
};
std::shared_ptr<Concept> self_;
};
// 工厂函数
Calculator create_adder();
Calculator create_multiplier();
}
关键设计要点总结
-
需要显式实例化,在
.cpp文件中为常用类型显式实例化模板 -
使用extern声明,在头文件中使用
extern template避免重复实例化 -
保持 ABI 稳定性,动态库接口保持稳定,模板参数类型固定
-
保持灵活性,提供header-only模式供用户自定义类型使用
-
做好版本控制,确保二进制兼容性
这种设计允许在动态库中提供常用类型的模板实现,同时保持用户自定义类型的灵活性。