库设计:允许用户在"仅标题"和动态链接之间做出决定?

Vit*_*meo 29 c++ library-design libraries header-only c++11

我创建了几个目前只有标头的 C++库.我的类的接口和实现都写在同一个.hpp文件中.

我最近开始认为这种设计不是很好:

  1. 如果用户想要编译库并动态链接它,他/她就不能.
  2. 更改单行代码需要完全重新编译依赖于库的现有项目.

我真的很喜欢只有头文件库的方面:所有函​​数都可以内联,并且它们非常容易包含在你的项目中 - 不需要编译/链接任何东西,只需要一个简单的#include指令.

是否有可能充分利用两个世界?我的意思是 - 允许用户选择他/她想要如何使用库.它还可以加快开发速度,因为我以"动态链接模式"处理库以避免荒谬的编译时间,并以"仅标题模式"发布我的成品以最大化性能.

第一个逻辑步骤是将接口和实现划分为.hpp.inl文件.

不过,我不确定如何前进.我已经看到很多库将LIBRARY_API宏添加到它们的函数/类声明中 - 可能需要类似的东西来允许用户选择?


我的所有库函数都以inline关键字为前缀,以避免"多个定义......"错误.我假设关键字将被文件中的LIBRARY_INLINE宏替换.inl?宏将解析inline为"仅标题模式",而不是"动态链接模式".

Hor*_*ing 15

初步说明:我假设是Windows环境,但这应该可以轻松转移到其他环境.

您的图书馆必须为四种情况做好准备:

  1. 用作仅限标头的库
  2. 用作静态库
  3. 用作动态库(导入函数)
  4. 构建为动态库(导出功能)

因此,让四个预处理器定义了这些情况:INLINE_LIBRARY,STATIC_LIBRARY,IMPORT_LIBRARY,和EXPORT_LIBRARY(这只是一个例子,你可能想使用一些复杂的命名方案).用户必须根据他/她想要的内容定义其中一个.

然后你可以这样写你的标题:

// foo.hpp

#if defined(INLINE_LIBRARY)
#define LIBRARY_API inline
#elif defined(STATIC_LIBRARY)
#define LIBRARY_API
#elif defined(EXPORT_LIBRARY)
#define LIBRARY_API __declspec(dllexport)
#elif defined(IMPORT_LIBRARY)
#define LIBRARY_API __declspec(dllimport)
#endif

LIBRARY_API void foo();

#ifdef INLINE_LIBRARY
#include "foo.cpp"
#endif
Run Code Online (Sandbox Code Playgroud)

您的实现文件看起来像往常一样:

// foo.cpp

#include "foo.hpp"
#include <iostream>

void foo()
{
    std::cout << "foo";
}
Run Code Online (Sandbox Code Playgroud)

如果INLINE_LIBRARY已定义,则函数将内联声明,并且实现将像.inl文件一样包含在内.

如果STATIC_LIBRARY已定义,则声明函数时不使用任何说明符,并且用户必须将.cpp文件包含在他/她的构建过程中.

如果IMPORT_LIBRARY已定义,则导入函数,并且不需要任何实现.

如果EXPORT_LIBRARY已定义,则导出函数,用户必须编译这些.cpp文件.

在静态/导入/导出之间切换是一个非常常见的事情,但我不确定是否在方程式中添加标题是一件好事.通常,有充分的理由来定义内联或不内联.

就个人而言,我喜欢把一切都变成.cpp文件,除非它真的有内联(如模板),或者是有意义的性能代价(非常小的功能,通常是单行).这减少了编译时间和 - 更重要的 - 依赖性.

但是,如果我选择内联定义内容,我总是把它放在单独的.inl文件中,只是为了保持头文件清晰易懂.


Bas*_*tch 5

它是特定于操作系统和编译器的。在具有最新GCC编译器(版本 4.9)的Linux 上,您可以使用过程间链接时优化生成静态库。

这意味着您g++ -O2 -flto在编译时和库链接时都构建了您的库,并且您g++ -O2 -flto在调用程序的编译和链接时都使用了您的库。