【问题标题】:Building a C++ template library with make使用 make 构建 C++ 模板库
【发布时间】:2021-01-09 15:25:29
【问题描述】:

我在将我的 C++ 库重写为模板形式时遇到了困难;主要问题涉及重新设计Makefile

在以前的状态下,当它是非模板时,我有:

  • 一个头文件lib.h,其中(受包含保护)我们有类和重载运算符的声明。此文件根本不包含任何其他库。
  • 一个实现文件lib.cpp,在该文件的顶部,我包含了来自标准库(cmath、iostream 等)的许多头文件,以及为此包含的头文件自定义库:#include "lib.h"
  • 一个Makefile,带有用于构建/安装库的命令:
CC = g++
CFLAGS = -O2
SRC = lib.cpp
HDR = $(SRC:.cpp=.h)
OBJ = $(SRC:.cpp=.o)
LIB = $(OBJ:.o=.a)

.PHONY: all install clean uninstall

# =========================================================
# Build
# =========================================================

all: $(LIB)

# Create an object file
$(OBJ):
    $(CC) $(CFLAGS) -c -o $@ $(SRC)

# Create a static library file
$(LIB): $(OBJ)
    ar rcs $@ $<

# =========================================================
# Install
# =========================================================

install: ~/lib/lib/$(LIB) ~/lib/include/$(HDR)

# Create top-level directory for the libraries
~/lib:
    mkdir -p $@;

# Create top-level directory for the static library files
~/lib/lib:
    mkdir -p $@;

# Create top-level directory for the headers
~/lib/include:
    mkdir -p $@;

# Copy the library file into the right directory
~/lib/lib/$(LIB): $(LIB) ~/lib/lib
    cp $< $@

# Copy the header file into the right directory
~/lib/include/$(HDR): $(HDR) ~/lib/include
    cp $< $@
  • 安装后,我的 CI (GH Actions) 将编译一个小型测试程序,其中包括我的库头 (#include &lt;lib.h&gt;),使用以下命令:
g++ -O0 -Wall --std=c++14 test.cpp -I ~/lib/include/ ~/lib/lib/lib.a -o test

此设置运行良好。


现在问题出现了,当我想将我的类重写为模板时。

按照in this post 的信息,我在我的库的头文件末尾添加了一个#include "lib.cpp"(在include 保护内,ofc)。通过该更改,我需要调整编译过程并且不要在命令行中提供我的实现文件(因为它已经包含在标头中,我必须避免重新定义错误)。这很好用。问题的核心现在在Makefile 中,在构建目标文件的命令中。当我尝试编译我的库的实现文件时,它包含标头,并且标头再次包含实现...我对this port 中的问题表示怀疑,他们建议从实现文件中删除标头包含。所以我这样做了,我评论了#include "lib.h"并尝试运行:

g++ -O2 -c -o lib.o lib.cpp

我最终会遇到很多 error: use of undeclared identifier 错误...

如何使用make 正确构建库?我的限制是库保留在两个单独的文件中:头文件和实现。我最终想要的是能够在我的进一步程序中#include &lt;lib.h&gt;

甚至可以创建归档对象文件吗? 共享对象库(.so)呢

【问题讨论】:

  • 使用典型的模板库,一切都在标题中。没有什么可构建或链接的。另请参阅:header-only library。为什么又要坚持拥有两个单独的文件?
  • 不可能将模板代码作为目标文件分发,除非您使用显式模板实例化(这是对库的实用性的重大限制)。试图通过在头文件中包含 cpp 文件来伪造它没有任何好处。你应该像其他人一样做一个只有标题的库。
  • 我的库的编译可能需要太多时间,只有标头的版本需要在每次下游发生任何变化时重新编译整个东西,我想避免这种情况。
  • 这就是模板库的本质——在使用特定的模板参数集实例化模板之前,无法编译代码。你不能同时拥有模板和单独的编译;选一个。将.cpp 文件包含到.h 文件中不会为您提供单独的编译 - 每当.cpp 文件更改时,包含它的所有内容(间接通过.h 文件)都需要重新编译。该文件具有.cpp 扩展名而不是.h 并没有改变这一事实 - 文件扩展名只是为了方便人类的命名约定,它们不具有神奇的属性
  • 这就是 模板 的本质,句号。您不能将模板编译为目标代码,只能将它们的实例化。正是使用库的代码决定了需要哪些实例化。是的,这就是 C++ 编译通常比 C 编译成本更高的原因之一。

标签: c++ templates makefile shared-libraries static-libraries


【解决方案1】:

我在我的库的头文件末尾添加了一个#include "lib.cpp"(在包含保护中,ofc)。

我不能说我认为你在那儿遵循的建议很多。旨在用作标头或在标头中使用的代码应适当命名(.h.hpp 或您的项目遵循的任何约定),并且适合直接编译的代码几乎不属于标头。也许您的转换涉及将后一种类型的所有内容更改为前一种类型,因此您可能想将lib.cpp 重命名为lib_impl.h,或类似的名称,并完全跳过编译它。也许吧。

但是请注意,如果您将实现代码命名和构造为标头,则它需要自己的包含保护。还要注意,它不能包含任何外部的非模板函数。如果是这样,那么对同一程序有贡献的两个单独的翻译单元不能同时包含头文件,因为这将导致重复的函数定义,尽管包含保护。如果没有外部的、非模板函数(也没有外部对象定义),那么尝试编译为对象文件是没有意义的,因为其中的任何函数都没有可访问的入口点。

如何使用make 正确构建库?

这与make 无关。这只是提供自动化。问题在于库本身的结构和您的期望。

我的约束是 该库 位于两个单独的文件中:header 和 实施

只有当实现包含外部对象或函数的任何实例化模板,或任何外部非模板函数或对象时,此约束才有意义。这些是有助于构建可构建目标文件的东西,您可以直接或间接链接到应用程序。如果转换为模板库意味着您的库中不再有任何此类实体,则约束是任意的,因为转换后的结果是仅包含标头的库。不过,您可以按照自己喜欢的方式拆分您的标题,只要每个生成的标题都是 as 标题,只有适合标题的内容,并且有自己的包含保护。

另一方面,如果您转换后的实现代码确实包含任何这些内容,那么它们不得将#included 放入任何标题中,如上所述。

我最终想要的是能够在我的进一步程序中#include &lt;lib.h&gt;

如果转换后的实现代码适合用作标头或在标头中使用,那么您已经在那里,但是将您的 lib.cpp 重命名为标头会更好。如果您希望能够将该头文件直接包含到主库头文件以外的代码中,那么它需要自己的包含保护——这将处理重复的声明。但是请注意,这些错误源于您具有循环依赖这一事实,这是您应该重构的强烈迹象。这样的重构将涉及将足够的代码从lib.cpp 移动到lib.h,从而可以删除循环依赖(其中一个文件将不再是#include 另一个)。

任何不适合在标头中使用的实现代码显然都不能包含在标头中。如果任何此类代码保留在转换后的库中,那么也许您将其保留在 lib.cpp 中,并将其余部分移至 lib.h

甚至可以创建归档对象文件吗?共享对象库(.so)呢

无法编译模板。它们是可编译代码的模板:它们的实例化。如果您的转换没​​有留下任何其他内容,那么您将无法有效地创建目标文件或共享库。而且你不需要这样做,因为这是一个只有头文件的库。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-13
    • 1970-01-01
    • 1970-01-01
    • 2018-08-29
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多