Skip to content

cpp 头文件中不能定义普通类的实例,但是可以定义lambda的实例

chlict edited this page Oct 5, 2020 · 1 revision

lambda实例重复定义并不违反单一定义原则

按照c++的单一定义原则,一个变量或者类的实例,在程序范围内只能有一处定义的地方。因此在一个头文件中,不宜对变量或者类的实例进行定义(可以有声明),否则当这个头文件被多个源文件include后,容易违背单一定义原则。但是lambda表达式的实例,确是例外。

// foo.h
// auto g_var = 10;    // Error: duplicated definition of g_var
auto fn = [](){ return 10; }; // OK: defines fn as an instance of a lambda
// file1.cpp
#include "foo.h"

int foo1() {
    return fn();
}
// file2.cpp
#include "foo.h"

int foo2() {
    return fn();
}

int main() {
}

编译:

g++ -std=c++14 -c file1.cpp
g++ -std=c++14 -c file2.cpp
g++ file1.o file2.o

可以正常编译并执行,可见fn的定义并不会引起重复定义问题。

内部符号

使用nm命令查看各符号的定义:

$ nm file1.o
0000000000000000 T __Z4foo1v
0000000000000020 t __ZNK3$_0clEv
00000000000000d8 b _fn
$ nm file2.o
0000000000000000 T __Z4foo2v
0000000000000020 t __ZNK3$_0clEv
0000000000000128 b _fn
0000000000000030 T _main
$ nm a.out
0000000100003f50 T __Z4foo1v
0000000100003f80 T __Z4foo2v
0000000100003f70 t __ZNK3$_0clEv
0000000100003fa0 t __ZNK3$_0clEv
0000000100000000 T __mh_execute_header
0000000100004000 b _fn
0000000100004001 b _fn
0000000100003fb0 T _main
                 U dyld_stub_binder

可见file1.o和file2.o中都有fn和__ZNK3$_0clEv这两个符号的定义。两者的tag都是小写字母,表示是内部符号,也正因如此,不会引发重复定义错误。"b"表示BSS段变量,"t"表示TEXT段的符号。对应的,foo1和foo2的tag是"T",也是TEXT段的符号,但具备外部链接属性。

另外值得注意的是fn的大小为1个字节。

inline函数

头文件中可以定义inline函数,同样不会引发重复定义错误:

foo.h中增加如下一行:

inline int inline_fn() { return 20; }

file1.cpp和file2.cpp中增加相应调用:

// file1.cpp
int foo1() {
    return fn() + inline_fn();
}
// file2.cpp
int foo2() {
    return fn() + inline_fn();
}

同样编译通过后,使用nm命令查看符号:

$ nm file1.o
0000000000000000 T __Z4foo1v
0000000000000040 T __Z9inline_fnv
0000000000000030 t __ZNK3$_0clEv
0000000000000140 b _fn
$ nm file2.o
0000000000000000 T __Z4foo2v
0000000000000040 T __Z9inline_fnv
0000000000000030 t __ZNK3$_0clEv
0000000000000190 b _fn
0000000000000050 T _main

可以看到,file1.o和file2.o中都多了一个tag为“T"的外部符号__Z9inline_fnv。但是链接后,在a.out中两者合并成了一个符号:

$ nm a.out
0000000100003f00 T __Z4foo1v
0000000100003f50 T __Z4foo2v
0000000100003f40 T __Z9inline_fnv
0000000100003f30 t __ZNK3$_0clEv
0000000100003f80 t __ZNK3$_0clEv
0000000100008008 d __dyld_private
0000000100000000 T __mh_execute_header
0000000100008010 b _fn
0000000100008011 b _fn
0000000100003f90 T _main

可见链接器对inline函数和lambda函数的处理方法不同。