购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.5 引用第三方库

我们的构建之旅已经涵盖了主要的构建目标类型,也快要接近尾声了。本节会介绍引用第三方库的方法。毕竟,使用C和C++编程的一大优势就是可以利用其丰富的生态,让我们站在巨人的肩膀上。说到C++引用第三方库,想必都绕不开Boost库。本节就将以Boost库的使用为例,演示如何引用第三方库。

1.5.1 下载Boost C++库

读者可以在Boost官方网站中找到针对UNIX和Windows的下载链接。如果使用的是Linux和macOS,那么也可以通过针对UNIX平台的下载链接下载。

下载压缩包并解压后,可以找到名称以boost开头的文件夹,boost后面的数字代表版本号,如1_74_0代表1.74.0版本。下载版本不同,文件夹名称也有所不同。本书将以1.74.0版本为例进行讲解。

解压文件夹以备后续使用。本书为了避免使用的Boost库版本与读者使用的不同从而造成指定目录的麻烦,假定解压后的boost_1_74_0文件夹被重命名为boost,不再体现版本号。该文件夹在Windows操作系统中被解压到C盘根目录,即C:\boost;在Linux操作系统中则被解压到Home目录中,即~/boost。

Boost库中有一些源程序,需要被编译成动态库或静态库来使用。但我们暂时不会用到这些编译后的库文件,因此Boost库的安装构建会在后续章节介绍。

1.5.2 引用Boost C++头文件库

首先来尝试使用Boost中的头文件库。头文件库(header-only library)指只包含头文件(.h、.hpp等)的程序库。使用这种库非常方便,只需在程序中引用它的头文件,无须对库本身进行额外的编译。源程序引用头文件,相当于复制了头文件的内容,这样头文件库实际上也就成为了引用它的程序的一部分。所以使用头文件库只需编译引用它的程序,头文件库代码会自动被编译。

除了用起来简单,头文件库在性能方面也更具有优势。这是因为它能够直接被程序以源代码的形式引用,编译器能够更好地进行代码优化,如实现更多的函数内联,有助于提升程序的整体性能。

但其缺点也很明显,那就是影响编译时间。因为头文件库本身没有源程序,无法独立编译成目标文件,再被链接到使用它的程序中,这就不可避免地需要反复编译头文件中的程序。另外,分发头文件库也意味着开源是必需的了,毕竟需要用户来编译。这反映了头文件库的封装性相对较差。

总而言之,对于较为常用且简单的库,尤其是追求极致性能的库,使用头文件库的形式非常合适。最典型的例子可能就是C++的标准模板库(Standard Template Library,STL)了。

Boost中也有很多头文件库,本小节将使用Boost字符串算法库(Boost string algorithms library)来编写例程。主程序main.cpp如代码清单1.22所示。

代码清单1.22 ch001/头文件库/main.cpp
#include <boost/algorithm/string.hpp>
#include <iostream>
 
using namespace std;
using namespace boost;
 
int main() {
    string str = "  hello world!";
    cout << str << endl;
 
    to_upper(str);
    cout << str << endl;
 
    trim(str);
    cout << str << endl;
 
    return 0;
}

引用boost/algorithm/string.hpp头文件即可使用Boost字符串算法库。它提供了很多方便操作字符串的函数。主程序中使用to_upper函数将str转换为大写,使用trim函数去除str首尾的空白字符。

使用MSVC/NMake构建本例

Makefile如代码清单1.23所示。

代码清单1.23 ch001/头文件库/NMakefile
main.exe: main.cpp
    cl main.cpp /I "C:\boost" /EHsc /Fe"main.exe"
 
clean:
    del *.obj *.exe

这里为编译器提供了参数/I "C:\boost",表示将C:\boost添加到编译器的头文件搜索目录中,以便找到Boost头文件。

使用GCC/make构建本例

Makefile如代码清单1.24所示。

代码清单1.24 ch001/头文件库/Makefile
main: main.cpp
    g++ main.cpp -I ~/boost -o main
 
clean:
    rm main

GCC设定头文件搜索目录的参数是-I,其他设置与NMake Makefile几乎一样。

1.5.3 安装Boost C++库

刚刚我们简单尝试了Boost的头文件库,这并不需要对Boost库本身进行编译。而后面的小节将链接 Boost的静态库,需要提前准备已经编译好的Boost库文件。我们可以自行构建Boost库,或者下载安装预编译的二进制文件。

在Windows中构建Boost库

打开Visual Studio的命令行工具,执行下列命令即可完成Boost库的构建:

> cd C:\boost
> bootstrap
> .\b2

构建过程较为耗时,请耐心等待。构建完成后,可以在C:\boost\stage\lib目录中看到所有构建好的Boost静态库。另外,在b2命令后追加参数link=shared,static即可同时构建动态库。此时,C:\boost\stage\lib目录中会同时存在静态库、动态库和导入库。由于静态库和导入库的扩展名都是.lib,Boost通过文件名前缀来辨别二者:lib开头的.lib文件是静态库,而那些与动态库文件名完全匹配的则是动态库的导入库。

在Linux中构建Boost库

在Linux中,构建Boost库的步骤几乎与Windows中一致,打开终端执行以下命令:

$ cd ~/boost
$ ./bootstrap.sh
$ ./b2

喝一杯茶再回来,应该就能在~/boost/stage/lib目录中看到所有构建好的Boost动态库及静态库了!

在Windows中安装预编译的Boost库

由于C++标准不保证编译后的应用程序二进制接口(Application Binary Interface,ABI)稳定性,不同版本的编译器编译出的程序无法保证相互引用而不出错。所以,我们必须根据微软C++工具集(Microsoft C++ Toolset)的版本号来决定安装哪个版本的预编译Boost库。读者如果不确定正在使用哪个版本的工具集,可以打开Visual Studio命令行工具,输出环境变量 VCToolsVersion:

> echo %VCToolsVersion%
14.27.29016

其中,主版本号14和次版本号的第一个数字2唯一决定其ABI稳定性。也就是说,如果Boost库是通过14.2*版本的工具集构建的,就能被上述“14.27.29016”版本的工具集引用。网络上有很多针对各个版本的工具集预编译好的Boost库二进制文件,下载时,一定要注意挑选匹配的版本,还要区分一下32位和64位的版本。

因为笔者用的是14.27.29016版本的工具集,所以下载的安装包是boost_1_74_0-msvc- 14.2-64.exe。下载完成后,运行安装程序将其安装到某一目录即可。本书假定预编译Boost库的安装根目录为C:\boost_prebuilt(注意区分自行构建的Boost库根目录C:\boost,后面会分别演示)。

在Ubuntu中安装预编译的Boost库

在Ubuntu发行版中可以直接通过包管理器直接安装预编译的Boost库:

$ sudo apt install libboost-all-dev

不过这样安装的只有头文件和动态库,分别位于/usr/include和/lib/x86_64- linux-gnu目录中。由于这些都是系统目录,即使不向编译器提供-I或-L参数,编译器也会默认在这里搜索头文件和库,非常方便。实际上,自行构建Boost库时,也可以通过./b2 install将Boost库安装到系统目录中。

在CentOS中安装预编译的Boost库

在CentOS发行版中同样可以通过包管理器安装预编译的Boost动态库:

$ sudo dnf install boost-devel

此时,安装好的头文件和动态库分别位于/usr/include和/usr/lib64系统目录中。

1.5.4 链接Boost C++库

无论是自己构建库,还是下载安装预编译的库,我们现在总算已经安装好了Boost静态库或动态库的二进制文件。接下来将借助它们完成更复杂的功能!本小节将使用Boost Regex库提取一段文本中出现的所有URL。

使用Boost Regex库提取URL

主程序main.cpp如代码清单1.25所示。

代码清单1.25 ch001/链接Boost/main.cpp
#include <boost/regex.hpp>
#include <iostream>
#include <string>
 
using namespace std;
using namespace boost;
 
int main() {
    string s = R"(
Search Engines: http://baidu.com https://google.com
About Me: https://xuhongxu.com/about/
    )";
    regex e(R"(([a-zA-Z]*)://[a-zA-Z0-9./]+)");
 
    for (sregex_iterator m(s.begin(), s.end(), e), end; m != end; ++m) {
        cout << "URL: " << (*m)[0].str() << endl;
        cout << "Scheme: " << (*m)[1].str() << endl;
        cout << endl;
    }
 
    return 0;
}

其中,首先引用了头文件boost/regex.hpp,然后在主程序中初始化了一个boost::Regex类型的变量,即用于提取URL的正则表达式:

([a-zA-Z]*)://[a-zA-Z0-9./]+

注意:由于该表达式仅用于演示,刻意写得较为简短,并不能准确提取URL。

for循环起始条件中,初始化了sregex_iterator迭代器m,用于遍历字符串中匹配到的全部结果;还有一个空迭代器end,用于指示迭代器的终止位置。迭代器的值,也就是匹配结果,采用类似数组的形式,可以通过索引访问。第0项为完全匹配的结果,后续索引项则依次是各个捕获组的结果。

使用MSVC/NMake构建本例

这里将构建两次主程序main.cpp,分别演示对Boost库的静态链接和动态链接。其中,静态链接Boost库的可执行文件名为static_boost.exe,动态链接Boost库的可执行文件名为shared_boost.exe。 Makefile如代码清单1.26所示。

代码清单1.26 ch001/链接Boost/NMakefile
# 自行构建的Boost库
 
BOOST_DIR=C:\boost
BOOST_LIB_DIR=$(BOOST_DIR)\stage\lib
 
# 下载安装的预编译Boost库
 
# BOOST_DIR=C:\boost_prebuilt
# BOOST_LIB_DIR=$(BOOST_DIR)\lib64-msvc-14.2
 
CXXFLAGS=/I $(BOOST_DIR) /MD /EHsc
LINKFLAGS=/LIBPATH:$(BOOST_LIB_DIR)
 
all: static_boost.exe shared_boost.exe
 
static_boost.exe: main.cpp 
    cl libboost_regex-vc142-mt-x64-1_74.lib \
        main.cpp $(CXXFLAGS) /Fe"static_boost.exe" /link $(LINKFLAGS)
 
shared_boost.exe: main.cpp 
    cl boost_regex-vc142-mt-x64-1_74.lib /DBOOST_ALL_NO_LIB \
        main.cpp $(CXXFLAGS) /Fe"shared_boost.exe" /link $(LINKFLAGS)
 
clean:
    del *.obj *.exe

CXXFLAGS变量用于向编译器传递公共参数。/I用于指定头文件搜索目录,这里直接设置为Boost的根目录即可。/MD参数代表程序将会动态链接C++运行时库,与之相对地,MSVC还有一个/MT参数,表示程序将会静态链接C++运行时库。由于Boost库的构建过程会默认指定/MD,这里引用Boost库的主程序也应该使用匹配的方式。

LINKFLAGS变量定义了向链接器传递的公共参数LIBPATH,即链接库的搜索目录。

下面是构建目标规则。由于本例将构建两个可执行文件,所以第一条规则将构建目标写为 all,同时依赖这两个可执行文件。这样,执行nmake all可以同时构建二者。另外,Makefile 的第一条规则是默认规则,当不提供目标参数执行nmake时会默认执行,因此执行nmake就相当于执行nmake all(不过对于本例来说,记得指定/F NMakefile参数)。

静态链接Boost库的主程序static_boost.exe的构建规则中,除了将CXXFLAGS和LINKFLAGS变量中定义的参数传递给编译器和链接器外,还向编译器传递了Boost Regex静态库的文件名。这与之前在代码清单1.12中静态链接自己编写的静态库几乎是一样的,仅仅是增加了指定搜索目录的参数。

动态链接Boost库的主程序shared_boost.exe的构建规则稍微复杂。与静态库相似但不同的是它所链接的.lib库是动态库对应的导入库。另外还多了一个宏的定义:BOOST_ALL_NO_LIB。这个宏用于指示Boost库不要试图寻找静态库进行链接,当动态链接Boost库时,都应该定义这个宏。

执行NMake构建该项目:

> cd CMake-Book\src\ch001\链接Boost
> nmake /F Makefile
> static_boost
URL: http://baidu.com
Scheme: http
 
URL: https://google.com
Scheme: https
 
URL: https://xuhongxu.com/about/
Scheme: https
 
> shared_boost # 无法启动

静态链接Boost库的主程序一切正常!但是,动态链接Boost库的主程序在运行时会抱怨找不到Boost 的动态库。这也是意料之中的事情,毕竟Boost的动态库与主程序并不在同一目录,而且Windows中也没有RUNPATH和RPATH,我们需要先复制动态库boost_regex-vc142-mt- x64-1_74.dll再运行。

使用GCC/GNU make构建本例

为了更好地对比,在Linux操作系统中,这里仍然以静态和动态两种链接Boost库的形式来构建本例。 Makefile如代码清单1.27所示。

代码清单1.27 ch001/链接Boost/Makefile
# 自行构建的Boost库
 
BOOST_DIR = $${HOME}/boost
BOOST_LIB_DIR = ${BOOST_DIR}/stage/lib
 
CXXFLAGS = -I $(BOOST_DIR)
LDFLAGS = -L $(BOOST_LIB_DIR) -Wl,-R$(BOOST_LIB_DIR)
 
# 将以上几行全部注释,即可使用安装的预编译Boost库
 
all: static_boost shared_boost
 
static_boost: main.cpp
    g++ main.cpp $(CXXFLAGS) $(LDFLAGS) -l:libboost_regex.a -o static_boost
 
shared_boost: main.cpp
    g++ main.cpp $(CXXFLAGS) $(LDFLAGS) -lboost_regex -o shared_boost
 
clean:
    rm *_boost

首先定义与Boost库目录相关的变量。BOOST_DIR是自行构建的Boost库所在的根目录,也就是~/boost;但由于RUNPATH需要使用绝对路径,我们将它写作$${HOME}。两个$代表$的转义,因此这里实际上引用了${HOME},它是代表Home目录绝对路径的环境变量。BOOST_LIB_DIR变量,与Windows中一样,定义了Boost库的库文件目录。

不过这里为什么不像NMake Makefile中一样,提供预编译库的路径变量呢?答案很简单,因为GCC会主动搜索系统的头文件目录和库文件目录,而系统包管理器安装的Boost预编译库正是安装在系统目录中。如果想让构建的程序直接链接它们,只需将Makefile 中前面这四个变量的定义注释掉,让GCC自动去默认的目录搜索头文件和库文件。

CXXFLAGS和LDFLAGS变量分别代表公共的编译和链接参数。编译参数-I指定了头文件库搜索目录,链接参数-L指定了链接库文件搜索目录,链接参数-Wl,-R 指定了RUNPATH的值。

最后,构建主程序的规则:无论是静态链接Boost库,还是动态链接Boost库,调用GCC的方式都是一样的,区别仅仅在于链接库的名称。由于链接库时,-l参数默认接受的是库的名称,而非文件名。所以,链接静态库libboost_regex.a或动态库libboost_regex.so时,应该指定参数-lboost_regex,这就冲突了,此时 GCC会优先链接动态库。为了能够实现对Boost静态库的链接,这里需要使用-l:加静态库文件全名的参数形式。

执行make构建该项目:

$ cd CMake-Book/src/ch001/链接Boost
$ make
$ ./static_boost
...
$ ./shared_boost
...

Linux中的程序可以指定RUNPATH,因此无须复制Boost动态库文件就可以运行shared_boost。 /+A79CDw+Tc4Ondi72zhpZmPNZkajWUECbqNyP0N8E+bt0ikz+U1jvxKLkarPNZc

点击中间区域
呼出菜单
上一章
目录
下一章
×