我今天遇到了一个有趣的问题。考虑这个简单的例子:
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中得到太多能够处理这种情况?或者这是我出于某种原因不应该做的事情?
答案 0 :(得分:60)
这是故意的,9.4.2 / 4说:
如果静态数据成员是const integral或const枚举类型, 它在课堂上的宣言 定义可以指定一个 恒定初始化器应该是一个 积分常数表达式(5.19)In 那种情况下,会员可以出现在 积分常数表达式。该 会员仍应在a。中定义 命名空间作用域,如果它在 程序
当你通过const引用传递静态数据成员时,你“使用”它,3.2 / 2:
可能会评估表达式 除非它出现在积分中 需要持续表达(见 5.19),是sizeof运算符的操作数(5.3.3),或者是操作数 typeid运算符和表达式 不指定左值 多态类型(5.2.8)。一个 对象或非重载函数是 如果其名称出现在a中,则使用 潜在评估的表达。
所以实际上,当你按价值或static_cast
传递它时,你会“使用”它。只是GCC让你在一个案例中脱离困境而不是另一个案例。
[编辑:gcc正在应用来自C ++ 0x草案的规则:“一个变量或非重载函数,其名称显示为一个可能被评估的表达式是odr-used,除非它是一个满足出现要求的对象在常量表达式(5.19)中,立即应用左值到右值的转换(4.1)。“静态强制转换立即执行lvalue-rvalue转换,因此在C ++ 0x中它不是“使用”。]
const引用的实际问题是foo
在获取其参数地址的权限范围内,并将其与例如来自另一个调用的参数的地址进行比较,存储在全局中。由于静态数据成员是唯一对象,这意味着如果从两个不同的TU调用foo(kConst)
,则在每种情况下传递的对象的地址必须相同。除非在一个(并且只有一个)TU中定义对象,否则AFAIK GCC不能安排。
好的,所以在这种情况下foo
是一个模板,因此定义在所有TU中都是可见的,因此编译器理论上可以排除它对地址做任何事情的风险。但一般来说,你当然不应该使用不存在的对象的地址或引用; - )
答案 1 :(得分:22)
如果您正在使用类声明中的初始化程序编写静态const变量,那就好像您已经编写了
class Bar
{
enum { kConst = 1 };
}
并且GCC会以同样的方式对待它,这意味着它没有地址。
正确的代码应该是
class Bar
{
static const int kConst;
}
const int Bar::kConst = 1;
答案 2 :(得分:12)
这是一个非常有效的案例。特别是因为 foo 可以是STL中的函数,例如 std :: count ,它将 const T&amp; 作为其第三个参数。
我花了很多时间试图理解链接器为什么会遇到这样的基本代码问题。
错误消息
未定义参考&#39; Bar :: kConst&#39;
告诉我们链接器找不到符号。
$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 U Bar::kConst
我们可以从“&#39; U&#39; 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
现在,你可以看到&#39; R&#39;说它是在数据部分中定义的。
答案 3 :(得分:2)
g ++版本4.3.4接受此代码(请参阅this link)。但g ++版本4.4.0拒绝它。
答案 4 :(得分:1)
我认为这个C ++的人工制品意味着只要引用Bar::kConst
,就会使用它的字面值。
这意味着在实践中没有变量可以作为参考点。
您可能必须这样做:
void func()
{
int k = kConst;
foo(k);
}
答案 5 :(得分:1)
简单技巧:在+
传递函数之前使用kConst
。这将阻止常量被引用,这样代码就不会生成对常量对象的链接器请求,但它将继续使用编译器时间常量值。
答案 6 :(得分:1)
C++17 中的正确方法是简单地创建变量 constexpr:
static constexpr int kConst = 1;
constexpr 静态数据成员是隐式内联的。
答案 7 :(得分:0)
我遇到了与Cloderic(三元运算符中的静态const:r = s ? kConst1 : kConst2
)提到的相同的问题,但是它仅在关闭编译器优化(-O0而不是-Os)后才抱怨。发生在gcc-none-eabi 4.8.5上。