工作 C++03 代码上的 G++ (C++14) 链接器错误

G++ (C++14) linker error on working C++03 code

考虑以下代码。

class aClass
{
public:
    static const int HALLO = -3;
};

int main()
{
  std::vector<double > a;
  std::vector<int> b;
  std::vector<int> c;
  int d = aClass::HALLO; //fine
  a.resize(10,aClass::HALLO); //fine
  b.resize(10,aClass::HALLO); // linker error c++11 and c++14
  c.resize(10,(int)(double)aClass::HALLO); //fine
  std::cout<<a[0]<<endl;
  std::cout<<b[0]<<endl;
  std::cout<<c[0]<<endl;
  return 0;
}

使用 C++03 编译并产生输出:

-3
-3
-3

但是,使用 C++11 或 C++14 编译会导致链接器错误:

/tmp/cc3BARzY.o: In Funktion `main':
main.cpp:(.text+0x66): Nicht definierter Verweis auf `aClass::HALLO'
collect2: error: ld returned 1 exit status

奇怪的是,这只发生在矢量 b 上。如果转换为 double(a) 或什至转换为 double 并返回 int (c),代码将按预期运行。

如何解释这种行为?

Pre-C++11 std::vector::resize() 的签名是

void resize( size_type count, T value = T() );

现在是

void resize( size_type count, const value_type& value );

从按值传递到按常量引用传递的更改导致调用站点变为 ODR-use aClass::HALLO,而以前没有。转换为 double 然后返回到 int 以一种避免 ODR 使用的方式产生一个临时的;对 a.resize() 的调用出于同样的原因,因为 int 值被隐式转换为 double 并且参数引用绑定到生成的临时值。

这里通常的解决方法是 provide a definition for aClass::HALLO;如果出于某种原因这对您来说是不受欢迎的,shorthand 用于产生临时以避免 ODR 使用的方法是应用一元 operator+:

b.resize(10, +aClass::HALLO);

为什么它适用于 double 向量,但不适用于 int 的原因很有趣。自 C++11 起,std::vector::resize 的签名是 void resize(size_type count, const value_type& value )。引用 object 使其成为 ODR-used,因此,您的静态 int 成员现在需要在您的应用程序中的某处定义。

但是,当您 std::vector<double> 时,您无法将引用绑定到 object。相反,编译器创建一个临时 double object 并将引用绑定到所述临时。因此,您避免 ODR-using class 的静态成员,因为创建 double 临时不会 ODR-use 它, ODR-using 临时就可以了.

如果您有 class 的 .cpp 文件,那么解决这个问题就很简单了,在这种情况下,您只需在那里定义静态文件。但是,对于 header-only class,解决方案在 C++17 之前并不简单,您可以在 C++17 中使用内联变量并获得非常好的解决方案:

#include <vector>

class aClass
{
public:
    static const int HALLO;
};

inline const int aClass::HALLO = -3;

int main()
{
  std::vector<int> b;
  b.resize(10,aClass::HALLO); //fine

  return 0;
}