任何人都可以解释为什么以下代码无法编译吗?至少在 g++ 4.2.4 上。
更有趣的是,为什么当我将 MEMBER 转换为 int 时它会编译?
#include <vector>
class Foo {
public:
static const int MEMBER = 1;
};
int main(){
vector<int> v;
v.push_back( Foo::MEMBER ); // undefined reference to `Foo::MEMBER'
v.push_back( (int) Foo::MEMBER ); // OK
return 0;
}
您需要在某处实际定义静态成员(在类定义之后)。试试这个:
class Foo { /* ... */ };
const int Foo::MEMBER;
int main() { /* ... */ }
这应该摆脱未定义的引用。
问题的出现是因为新的 C++ 功能和您想要做的事情之间存在有趣的冲突。首先我们来看看
push_back
签名:
void push_back(const T&)
它需要对类型为
T
的对象的引用。在旧的初始化系统下,存在这样的成员。例如,以下代码可以正常编译:
#include <vector>
class Foo {
public:
static const int MEMBER;
};
const int Foo::MEMBER = 1;
int main(){
std::vector<int> v;
v.push_back( Foo::MEMBER ); // undefined reference to `Foo::MEMBER'
v.push_back( (int) Foo::MEMBER ); // OK
return 0;
}
这是因为某个地方有一个实际的对象,其中存储了该值。但是,如果您切换到指定静态 const 成员的新方法(如上面所示),则
Foo::MEMBER
不再是对象。它是一个常数,有点类似于:
#define MEMBER 1
但是没有预处理器宏的麻烦(并且具有类型安全性)。这意味着需要引用的向量无法获得引用。
如果需要定义静态 const 成员,C++ 标准需要该定义。
定义是必需的,例如,如果使用它的地址。
push_back
通过 const 引用获取其参数,因此编译器严格需要您的成员的地址,并且您需要在命名空间中定义它。
当您显式转换常量时,您正在创建一个临时变量,并且这个临时变量绑定到引用(根据标准中的特殊规则)。
这是一个非常有趣的案例,我实际上认为值得提出一个问题,以便将 std 更改为您的常量成员具有相同的行为!
尽管如此,以一种奇怪的方式,这可以被视为一元“+”运算符的合法使用。基本上
unary +
的结果是一个右值,因此应用将右值绑定到 const 引用的规则,并且我们不使用静态 const 成员的地址:
v.push_back( +Foo::MEMBER );
啊啊.h
class Aaa {
protected:
static Aaa *defaultAaa;
};
Aaa.cpp
// You must define an actual variable in your program for the static members of the classes
static Aaa *Aaa::defaultAaa;
在 C++17 中,使用
inline
变量有一个更简单的解决方案:
struct Foo{
inline static int member;
};
这是
member
的定义,而不仅仅是它的声明。与内联函数类似,不同翻译单元中的多个相同定义不会违反 ODR。不再需要选择最喜欢的 .cpp 文件进行定义。
只是一些附加信息:
C++ 允许将整型和枚举类型的 const 静态类型“定义”为类成员。但这实际上不是一个定义,只是一个“初始化标记”
您仍然应该在课堂之外编写成员的定义。
9.4.2/4 - 如果静态数据成员是 const 整型或 const 枚举类型,则其在类定义中的声明可以指定常量初始值设定项,该常量初始值设定项应为整型常量表达式 (5.19)。在这种情况下,成员可以出现在整型常量表达式中。如果在程序中使用该成员,则仍应在命名空间范围中定义该成员,并且命名空间范围定义不应包含初始值设定项。
不知道为什么强制转换有效,但是 Foo::MEMBER 直到第一次加载 Foo 时才分配,并且由于您从未加载它,所以它永远不会分配。如果您在某处引用了 Foo,它可能会起作用。
使用 C++11,上述对于基本类型来说是可能的
class Foo {
public:
static constexpr int MEMBER = 1;
};
constexpr
部分创建一个静态表达式,而不是静态变量——其行为就像一个极其简单的内联方法定义。不过,事实证明,在模板类中使用 C 字符串常量表达式时,该方法有点不稳定。
关于第二个问题:push_ref 将引用作为参数,并且不能引用类/结构的 static const 成员。一旦调用 static_cast,就会创建一个临时变量。并且可以传递对此对象的引用,一切正常。
或者至少解决这个问题的我的同事是这么说的。