设为首页 加入收藏

TOP

为什么标准库的模板变量都是inline的(一)
2023-07-23 13:34:44 】 浏览:90
Tags:都是 inline

最近在看标准库里的type_traits的时候发现了个有趣的地方,几乎所有在标准库里的变量模板都是inline的!

不仅常见的实现上(libstdc++、libc++、ms stl)都是inline的,标准里给的形式定义也是inline的。

比如微软开源的stl实现:https://github.com/microsoft/STL/blob/main/stl/inc/type_traits#L73

_EXPORT_STD template <class _Trait>
_INLINE_VAR constexpr bool negation_v = negation<_Trait>::value;

_EXPORT_STD template <class _Ty>
_INLINE_VAR constexpr bool is_void_v = is_same_v<remove_cv_t<_Ty>, void>;

其中_INLINE_VAR这个宏的实现在这里:

// P0607R0 Inline Variables For The STL
#if _HAS_CXX17
#define _INLINE_VAR inline
#else // _HAS_CXX17
#define _INLINE_VAR
#endif // _HAS_CXX17

可以看到如果编译器支持c++17的话这些模板变量就是inline的。

为什么要这样做呢?如果不使用inline又会要什么后果呢?带着这些疑问我们接着往下看。

c++的linkage

首先复习下c++的linkage,国内一般会翻译成“链接性”。因为篇幅有限,所以我们不关注“无链接”、“语言链接”和“模块链接”,只关注内部链接外部链接这两个。

内部链接(internal linkage):符号(粗暴得理解成变量,函数,类等等有名字的东西)仅仅在当前编译单元内部可见,不同编译单元之间可以存在同名的符号,他们是不同实体。

看个例子:

// value.h
static std::size_t a = 1;

// a.cpp
#include "value.h"

void f() {
    std::cout << "f() address of a: " << &a << "\n";
}

// b.cpp
#include "value.h"

void g() {
    std::cout << "g() address of a: " << &a << "\n";
}

// main.cpp
void f();
void g();

int main() {
    f();
    g();
}

注意,不要像上面那样写代码,尤其是把具有内部链接的非常量变量写在头文件里。编译并运行:

$ g++ -Wall -Wextra a.cpp b.cpp main.cpp
$ ./a.out

f() address of a: 0x564b7892e004
g() address of a: 0x564b7892e01c

可以看到确实有两个不同的实体存在。内部链接最大的好处在于可以实现一定程度上的隔离,但缺点是要付出生成文件体积和运行时内存上的代价,且不如命名空间和模块好使。

这个例子可能看不出,因为只有两个编译单元用了这个模板变量,所以只浪费了一个size_t的内存,在我的机器上是8字节。但项目里往往有成百上千甚至上万个编译单元,而且使用的变量不止一个,那么浪费的资源就很可观了。

外部链接(external linkage):符号可以被所以编译单元看见,且只能被定义一次。

例子:

// value.h
// extern std::size_t a = 1; 这么写是声明的同时定义了a,在头文件里这么干会导致a重复定义
extern int a;

// a.cpp
#include "value.h"

std::size_t a = 1; // 随便在哪定义都行,但只能定义一次

void f() {
    std::cout << "f() address of a: " << &a << "\n";
}

// b.cpp
#include "value.h"

void g() {
    std::cout << "g() address of a: " << &a << "\n";
}

// main.cpp
void f();
void g();

int main() {
    f();
    g();
}

编译并运行:

$ g++ -Wall -Wextra a.cpp b.cpp main.cpp
$ ./a.out

f() address of a: 0x55f5825f8040
g() address of a: 0x55f5825f8040

可以看到这时候就只有一个实体了。

那么什么样的东西会有内部链接,什么又有外部链接呢?

内部链接:所有匿名命名空间里的东西(哪怕声明成extern) + 标记成static的变量、变量模板、函数、函数模板 + 不是模板不是inline没有volatile或extern修饰的常量(const和constexpr)。

外部链接:非static函数、枚举和类天生有外部链接,除非在匿名命名空间里 + 排除内部链接规定的之后剩下的所有模板

说了半天,这和标准库用inline变量有什么关系吗?

还真有,因为内部链接最后一条规则那里的“非模板和非内联”是c++17才加入的,而模板变量c++14就有了,所以一个很麻烦的问题出现了:

template <typename T>
constexpr bool is_void_t = is_void<T>::value;

在这里is_void_t按照c++14的规则,可以是内部链接的。这样有什么问题?一般来说问题不大,编译器会尽可能把常量全部优化掉,但在这个常量被ODR-used(比如取地址或者绑定给函数的引用参数),这个常量就没法直接优化掉了,编译器只能乖乖地生产两个is_void_t的实例。而且这个is_void_t必须是常量,否则可以任意修改它的值,以及不是编译期常量的话没法在其他的模板里使用。

另一个问题在于,c++14忘记更新ODR原则的定义,漏了变量模板,虽然g++上变量模板和其他模板一样可以存在多次定义,但因为标准里没给出具体说法所以存在很大的风险。

c++社区最喜欢的一句格言是:“Don't pay for what you don't use.”

所以c++17的一个提案在增加了inline变量之后建议标准库里把模板变量和static constexpr都改为inline constexprhttps://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0607r0.html

inline变量

为什么提案里加上inline就能解决问题了呢?这就要了解下inline

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇<六>关于虚函数和动态绑定 下一篇<四>虚函数 静态绑定 动态..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目