未定义的引用静态const int
今天我遇到了一个有趣的问题。 考虑这个简单的例子:
template <typename T> void foo(const T & a) { /* code */ } // This would also fail // void foo(const int & a) { /* code */ } class Bar { public: static const int kConst = 1; void func() { foo(kConst); // This is the important line } }; int main() { Bar b; b.func(); }
编译时出现错误:
Undefined reference to 'Bar::kConst'
现在,我敢肯定,这是因为static const int
没有在任何地方定义,这是故意的,因为根据我的理解,编译器应该能够在编译时进行replace,而不需要定义。 然而,由于该函数采用了一个const int &
参数,它似乎没有进行替代,而宁愿参考。 我可以通过进行以下更改来解决此问题:
foo(static_cast<int>(kConst));
我相信这是现在迫使编译器做一个临时的int,然后传递一个引用,它可以在编译时成功完成。
我想知道这是故意的,还是我希望gcc能够处理这种情况呢? 或者这是我出于某种原因不应该做的事情?
这是故意的,9.4.2 / 4说:
如果一个静态数据成员是const整型或者const枚举types,那么它在类定义中的声明可以指定一个常数初始值设定项,它应该是一个整型常量expression式(5.19)。在这种情况下,成员可以出现在整型常量expression式中。 如果在程序中使用该成员,则该成员仍应在名称空间范围内定义
当你通过const引用传递静态数据成员时,你“使用”它,3.2 / 2:
除非出现需要整型常量expression式(见5.19),sizeof运算符(5.3.3)的操作数,或者是typeid运算符的操作数,并且expression式不指定左值多态类types(5.2.8)。 如果对象或非重载函数的名称出现在潜在评估的expression式中,则使用该函数。
所以实际上,当你通过值传递的时候,或者在static_cast
,“使用”它。 只是海湾合作委员会让你在一个案件中脱钩,而不是另一个。
[编辑:gcc是应用从C ++ 0x草案的规则:“一个variables或非重载函数的名称显示为潜在评估的expression式是odr使用,除非它是一个对象满足要求出现在一个常量expression式(5.19)和左值到右值的转换(4.1)立即应用。“ 静态转换立即执行左值右值转换,所以在C ++ 0x中不是“使用”。]
const引用的实际问题是, foo
在它的权限范围内,并将其与参数的地址进行比较,例如将其与另一个调用的参数的地址进行比较,存储在全局中。 由于静态数据成员是唯一的对象,这意味着如果您从两个不同的TU调用foo(kConst)
,则传递的对象的地址必须相同。 AFAIK GCC不能安排,除非对象被定义在一个(并且只有一个)TU中。
好的,在这种情况下, foo
是一个模板,因此定义在所有TU中都是可见的,所以编译器在理论上可能会排除它对地址做任何事情的风险。 但总的来说,你当然不应该把地址或引用不存在的对象;-)
如果你在类声明中使用初始值设定项来编写静态常量variables,就好像你写了一样
class Bar { enum { kConst = 1 }; }
GCC会以同样的方式对待它,这意味着它没有地址。
正确的代码应该是
class Bar { static const int kConst; } const int Bar::kConst = 1;
这是一个非常有效的案例。 特别是因为foo可能是STL中的一个函数,就像std :: count ,它把const T&作为第三个参数。
我花了很多时间试图理解为什么链接器有这样一个基本代码的问题。
错误消息
对“Bar :: kConst”的未定义引用
告诉我们链接器找不到符号。
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 U Bar::kConst
我们可以从'U'看到Bar :: kConst是未定义的。 因此,当链接器尝试工作时,必须find符号。 但是你只声明 kConst而不定义它。
C ++中的解决scheme也是如下定义它:
template <typename T> void foo(const T & a) { /* code */ } class Bar { public: static const int kConst = 1; void func() { foo(kConst); // This is the important line } }; const int Bar::kConst; // Definition <--FIX int main() { Bar b; b.func(); }
然后,你可以看到编译器会把定义放到生成的目标文件中:
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 R Bar::kConst
现在,您可以看到“R”表示它在数据部分定义。
g ++版本4.3.4接受这个代码(见这个链接 )。 但g ++版本4.4.0拒绝它。
我认为这个C ++的artefact意味着任何时候Bar::kConst
被引用,它的文字值被用来代替。
这意味着在实践中没有变数作为参考点。
您可能需要这样做:
void func() { int k = kConst; foo(k); }
简单的窍门:在kConst
传递函数之前使用+
。 这将阻止常量被引用,这样代码将不会生成对常量对象的链接器请求,而是会继续使用编译器时间常量值。