给出下面的代码,Foo::FOO1
是否使用了ODR?
#include <iostream>
#include <map>
#include <string>
class Foo
{
public:
static constexpr auto FOO1 = "foo1";
void bar();
};
void Foo::bar()
{
const std::map<std::string, int> m = {
{FOO1, 1},
};
for (auto i : m)
{
std::cout << i.first << " " << i.second << std::endl;
}
}
int main()
{
Foo f;
f.bar();
return 0;
}
使用-O1
或更高版本编译代码,可以,但如果使用-O0
编译,我会得到以下错误(参见 coliru 示例:
undefined reference to `Foo::FOO1'
这表明它正在使用 ODR。是哪个?
我知道上面的代码使用 -O 构建得很好,但在真实(且更复杂的(情况下:
- 代码编译并与 -O2 链接良好
- 代码得到上述
undefined reference
错误链接时间优化 (-O2 -flto
(
所以它表明优化(-O
(和链接时间优化(-flto
(都会影响ODR使用规则吗?这在 C++14 和 C++17 之间会发生变化吗?
规则是 [basic.def.odr]/4:
一个变量
x
,其名称显示为潜在计算表达式ex
由ex
使用 odr,除非对x
应用左值到右值的转换会产生一个不调用任何非平凡函数的常量表达式,并且如果x
是一个对象,则ex
是表达式e
的潜在结果集合的元素, 其中左值到右值的转换([conv.lval](应用于e
,或者e
是丢弃的值表达式([expr.prop](。
第一部分显然是满足的(FOO1
是constexpr
所以左值到右值的转换确实在不调用非平凡函数的情况下产生了一个常量表达式(,但第二部分呢?
我们正在建造一个map
.那里的相关构造函数需要一个initializer_list<value_type>
,也就是说一个initializer_list<pair<const string, int>>
。pair
有一堆构造函数,但这里要调用的是:
template <class U1, class U2>
constexpr pair(U1&& x, U2&& y); // with U1 = char const*&, U2 = int
这里重要的部分是我们不直接构造一个string
,我们正在通过这个pair
的转换构造函数,它涉及绑定对FOO1
的引用。这是一个 odr 用途。这里没有左值到右值的转换,也不是丢弃的值表达式。
基本上,当你获取某物的地址时,这是一个 odr 使用 - 它必须有一个定义。所以你必须添加一个定义:
constexpr char const* Foo::FOO1;
另一方面,请注意:
std::string s = FOO1;
不会是 ODR 使用。在这里,我们直接调用一个采用char const*
参数的构造函数,这将是一个左值到重值的转换。
在C++17中,我们在[dcl.constexpr]中得到了这个新句子:
使用 constexpr 说明符声明的函数或静态数据成员隐式是内联函数或变量 ([dcl.inline](。
这不会改变任何关于 odr-use 的内容,FOO1
在您的程序中仍然使用 odr。但它确实使FOO1
隐式成为内联变量,因此您不必为其显式添加定义。很酷。
另请注意,仅仅因为程序编译和链接并不意味着缺少定义的变量没有被 odr 使用。
所以它表明优化(-O(和LinkTimeOptimization(-flto(都会影响ODR使用规则吗?
他们没有。优化就是这样很酷。