未定义的对静态const int的引用

Jar*_*edC 72 c++ gcc

我今天遇到了一个有趣的问题.考虑这个简单的例子:

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();
}
Run Code Online (Sandbox Code Playgroud)

编译时我收到一个错误:

Undefined reference to 'Bar::kConst'
Run Code Online (Sandbox Code Playgroud)

现在,我很确定这是因为static const int没有在任何地方定义,这是有意的,因为根据我的理解,编译器应该能够在编译时进行替换而不需要定义.但是,由于函数接受一个const int &参数,它似乎没有进行替换,而是更喜欢引用.我可以通过进行以下更改来解决此问题:

foo(static_cast<int>(kConst));
Run Code Online (Sandbox Code Playgroud)

我相信这现在迫使编译器创建一个临时的int,然后传递一个引用,它可以在编译时成功完成.

我想知道这是故意的,还是我期望从gcc中得到太多能够处理这种情况?或者这是我出于某种原因不应该做的事情?

Ste*_*sop 60

这是故意的,9.4.2/4说:

如果静态数据成员是const integer或const枚举类型,则它在类定义中的声明可以指定一个常量初始化器,它应该是一个整型常量表达式(5.19)在这种情况下,该成员可以出现在整型常量表达式中.如果在程序中使用该成员,则仍应在命名空间范围内定义该成员

当你通过const引用传递静态数据成员时,你"使用"它,3.2/2:

表达式可能被评估,除非它出现在需要整数常量表达式的地方(见5.19),是sizeof运算符的操作数(5.3.3),或者是typeid运算符的操作数,并且表达式没有指定左值多态类型(5.2.8).如果对象或非重载函数的名称出现在可能已评估的表达式中,则使用该函数.

所以实际上,当你按价值传递它时,你也可以"使用"它static_cast.只是GCC让你在一个案例中脱离困境而不是另一个案例.

[编辑:gcc正在应用来自C++ 0x草案的规则:"一个变量或非重载函数,其名称显示为一个可能被评估的表达式是odr-used,除非它是一个满足出现在常量中的要求的对象表达式(5.19)和左值 - 右值转换(4.1)立即应用." 静态强制转换立即执行lvalue-rvalue转换,因此在C++ 0x中它不是"已使用".

const引用的实际问题在于foo它有权获取其参数的地址,并将其与例如来自另一个调用的参数的地址进行比较,存储在全局中.由于静态数据成员是唯一对象,这意味着如果foo(kConst)从两个不同的TU 调用,则在每种情况下传递的对象的地址必须相同.除非在一个(且仅一个)TU中定义对象,否则AFAIK GCC不能安排.

好的,所以在这种情况下foo是一个模板,因此定义在所有TU中都是可见的,因此编译器理论上可以排除它对地址做任何事情的风险.但一般来说,你当然不应该使用不存在的对象的地址或引用;-)

  • ...和 ​​std::min / std::max,这让我来到了这里! (2认同)

pel*_*lya 25

如果您在类声明中使用初始化程序编写静态const变量,就像您编写的那样

class Bar
{
      enum { kConst = 1 };
}
Run Code Online (Sandbox Code Playgroud)

GCC会以同样的方式对待它,这意味着它没有地址.

应该是正确的代码

class Bar
{
      static const int kConst;
}
const int Bar::kConst = 1;
Run Code Online (Sandbox Code Playgroud)


Sta*_*tac 12

这是一个非常有效的案例.特别是因为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
Run Code Online (Sandbox Code Playgroud)

我们可以从'U'看到Bar :: kConst是未定义的.因此,当链接器尝试完成其工作时,它必须找到符号.但是你只声明了 kConst并且没有定义它.

C++中的解决方案也是如下定义它:

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();
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以看到编译器将定义放在生成的目标文件中:

$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst
Run Code Online (Sandbox Code Playgroud)

现在,您可以看到"R"表示它已在数据部分中定义.


Gui*_*cot 7

C++17 中的正确方法是简单地将变量设置为 constexpr:

static constexpr int kConst = 1;
Run Code Online (Sandbox Code Playgroud)

constexpr 静态数据成员是隐式内联的。