工作 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;
}
考虑以下代码。
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;
}