提问人:JaredC 提问时间:3/22/2011 最后编辑:JaredC 更新时间:7/15/2021 访问量:46198
对静态 const int 的未定义引用
Undefined reference to static 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
const int &
foo(static_cast<int>(kConst));
我相信这现在迫使编译器创建一个临时的 int,然后传递对它的引用,它可以在编译时成功完成。
我想知道这是故意的,还是我对 gcc 的期望太高了,无法处理这种情况?还是出于某种原因我不应该这样做?
答:
我认为 C++ 的这个工件意味着任何时候引用它时,都会使用它的文字值。Bar::kConst
这意味着在实践中,没有变量可以作为参考点。
您可能需要这样做:
void func()
{
int k = kConst;
foo(k);
}
评论
foo(static_cast<int>(kConst));
这是故意的,9.4.2/4 说:
如果静态数据成员是 const integral 或 const 枚举类型, 它在类中的声明 定义可以指定一个 constant-initializer,它应该是 积分常数表达式 (5.19) 在 在这种情况下,该成员可以出现在 积分常量表达式。这 成员仍应在 命名空间范围(如果用于 程序
当您通过 const 引用传递静态数据成员时,您将“使用”它,3.2/2:
可能会计算表达式 除非它出现在积分 常量表达式是必需的(请参阅 5.19),是 sizeof 运算符 (5.3.3) 的操作数,或者是 TypeId 运算符和表达式 不指定 多态类类型 (5.2.8)。一 对象或非重载函数是 如果其名称出现在 潜在评估的表达式。
所以事实上,当你也按值传递它时,你“使用”它,或者在 .只是 GCC 在一种情况下让你摆脱了困境,但在另一种情况下却没有。static_cast
[编辑:gcc 正在应用 C++0x 草案中的规则:“名称显示为潜在计算表达式的变量或非重载函数是 odr 使用的,除非它是满足常量表达式 (5.19) 中出现的要求的对象,并且立即应用左值到右值的转换 (4.1)。静态转换会立即执行左值-右值转换,因此在 C++0x 中它不会“使用”。
const 引用的实际问题是,它有权获取其参数的地址,并将其与存储在全局中的另一个调用的参数地址进行比较。由于静态数据成员是唯一的对象,这意味着如果从两个不同的 TU 调用,则传递的对象的地址在每种情况下都必须相同。AFAIK GCC 无法安排它,除非对象在一个(且只有一个)TU 中定义。foo
foo(kConst)
好的,所以在这种情况下是一个模板,因此定义在所有 TU 中都是可见的,所以也许编译器在理论上可以排除它对地址做任何事情的风险。但总的来说,你当然不应该对不存在的对象进行地址或引用;-)foo
评论
template <int N> int intvalue() { return N; }
intvalue<kConst>
kConst
kConst
kConst
r = s ? kConst1 : kConst2
if
G++ 版本 4.3.4 接受此代码(请参阅此链接)。但是 g++ 版本 4.4.0 拒绝了它。
如果你在类声明中使用初始值设定项编写静态 const 变量,这就像你编写了
class Bar
{
enum { kConst = 1 };
}
GCC 将以同样的方式对待它,这意味着它没有地址。
正确的代码应该是
class Bar
{
static const int kConst;
}
const int Bar::kConst = 1;
评论
这是一个非常有效的案例。特别是因为 foo 可以是 STL 中的函数,例如 std::count,它采用常量 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 是未定义的。因此,当链接器尝试完成其工作时,它必须找到符号。但是你只声明了 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();
}
然后,您可以看到编译器将定义放在生成的对象文件中:
$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst
现在,您可以看到“R”表示它是在数据部分定义的。
评论
>nm -C main.o | grep kConst
0000000000400644 R Bar::kConst
nm -C lib.a
Constants.o: 0000000000000000 R Bar::kConst
main_file.o: U Bar::kConst ...
简单诀窍:使用前传下来的功能。这将防止从中获取常量的引用,这样代码就不会生成对常量对象的链接器请求,而是会继续使用编译器时间常量值。+
kConst
评论
static const
static_cast<decltype(kConst)>(kConst)
foo()
您也可以将其替换为 constexpr 成员函数:
class Bar
{
static constexpr int kConst() { return 1; };
};
评论
我遇到了 Cloderic 提到的相同问题(三元运算符中的静态常量:),但它只是在关闭编译器优化(-O0 而不是 -Os)后才抱怨。发生在 gcc-none-eabi 4.8.5 上。r = s ? kConst1 : kConst2
在 C++17 中,正确的方法是简单地创建变量 constexpr:
static constexpr int kConst = 1;
constexpr 静态数据成员是隐式内联的。
评论
const int kConst = 1;
const int &
int
static
std::min( some_val, kConst)
std::min<T>
T const &
foo(kConst+0);