我今天遇到了一个有趣的问题.考虑这个简单的例子:
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中都是可见的,因此编译器理论上可以排除它对地址做任何事情的风险.但一般来说,你当然不应该使用不存在的对象的地址或引用;-)
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"表示它已在数据部分中定义.
C++17 中的正确方法是简单地将变量设置为 constexpr:
static constexpr int kConst = 1;
Run Code Online (Sandbox Code Playgroud)
constexpr 静态数据成员是隐式内联的。