对静态类成员的未定义引用
谁能解释为什么以下代码无法编译? 至少在 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;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
您需要在某处实际定义静态成员(在类定义之后)。 试试这个:
这应该摆脱未定义的引用。
You need to actually define the static member somewhere (after the class definition). Try this:
That should get rid of the undefined reference.
问题的出现是由于新的 C++ 功能和您想要做的事情之间存在有趣的冲突。 首先,让我们看一下
push_back
签名:它需要对
T
类型的对象的引用。 在旧的初始化系统下,存在这样的成员。 例如,以下代码编译得很好:这是因为某处有一个实际对象,其中存储了该值。 但是,如果您切换到指定静态 const 成员的新方法(如上面所示),则
Foo::MEMBER
不再是对象。 它是一个常量,有点类似于:但没有预处理器宏的麻烦(并且具有类型安全性)。 这意味着需要引用的向量无法获得引用。
The problem comes because of an interesting clash of new C++ features and what you're trying to do. First, let's take a look at the
push_back
signature:It's expecting a reference to an object of type
T
. Under the old system of initialization, such a member exists. For example, the following code compiles just fine:This is because there is an actual object somewhere that has that value stored in it. If, however, you switch to the new method of specifying static const members, like you have above,
Foo::MEMBER
is no longer an object. It is a constant, somewhat akin to:But without the headaches of a preprocessor macro (and with type safety). That means that the vector, which is expecting a reference, can't get one.
如果出于某种原因需要定义,则 C++ 标准要求静态 const 成员的定义。
该定义是必需的,例如,如果使用它的地址。
push_back
通过 const 引用获取其参数,因此编译器严格地需要您的成员的地址,并且您需要在命名空间中定义它。当您显式转换常量时,您正在创建一个临时变量,并且这个临时变量绑定到引用(根据标准中的特殊规则)。
这是一个非常有趣的案例,我实际上认为值得提出一个问题,以便将 std 更改为对您的常量成员具有相同的行为!
虽然,以一种奇怪的方式,这可以被视为一元“+”运算符的合法使用。 基本上,一元 + 的结果是一个右值,因此将右值绑定到 const 引用的规则适用,并且我们不使用静态 const 成员的地址:
The C++ standard requires a definition for your static const member if the definition is somehow needed.
The definition is required, for example if it's address is used.
push_back
takes its parameter by const reference, and so strictly the compiler needs the address of your member and you need to define it in the namespace.When you explicitly cast the constant, you're creating a temporary and it's this temporary which is bound to the reference (under special rules in the standard).
This is a really interesting case, and I actually think it's worth raising an issue so that the std be changed to have the same behaviour for your constant member!
Although, in a weird kind of way this could be seen as a legitimate use of the unary '+' operator. Basically the result of the
unary +
is an rvalue and so the rules for binding of rvalues to const references apply and we don't use the address of our static const member:Aaa.h
Aaa.cpp
Aaa.h
Aaa.cpp
在 C++17 中,使用内联变量有一个更简单的解决方案:
这是
member
的定义,而不仅仅是其声明。 与内联函数类似,不同翻译单元中的多个相同定义不会违反 ODR。 不再需要选择最喜欢的 .cpp 文件进行定义。In C++17, there is an easier solution using
inline
variables:This is a definition of
member
, not just its declaration. Similar to inline functions, multiple identical definitions in different translation units do not violate ODR. There is no longer any need to pick a favourite .cpp file for the definition.只是一些附加信息:
C++ 允许将整型和枚举类型的 const 静态类型“定义”为类成员。 但这实际上不是一个定义,只是一个“初始化标记”。
您仍然应该在类之外编写成员的定义。
- [class.static.data] p4
Just some additional info:
C++ allows to "define" const static types of integral and enumeration types as class members. But this is actually not a definition, just an "initializiation-marker"
You should still write a definition of your member outside of the class.
- [class.static.data] p4
使用 C++11,上述内容对于基本类型是可能的,因为
constexpr
部分创建静态表达式,而不是静态变量 -它的行为就像一个极其简单的内联方法定义。 不过,事实证明,在模板类中使用 C 字符串常量表达式时,该方法有点不稳定。With C++11, the above would be possible for basic types as
The
constexpr
part creates a static expression as opposed to a static variable - and that behaves just like an extremely simple inline method definition. The approach proved a bit wobbly with C-string constexprs inside template classes, though.不知道为什么强制转换有效,但是 Foo::MEMBER 直到第一次加载 Foo 时才被分配,并且由于您从未加载它,所以它永远不会被分配。 如果您在某处引用了 Foo,它可能会起作用。
No idea why the cast works, but Foo::MEMBER isn't allocated until the first time Foo is loaded, and since you're never loading it, it's never allocated. If you had a reference to a Foo somewhere, it would probably work.
关于第二个问题:push_ref 将引用作为参数,并且不能引用类/结构的 static const 成员。 一旦调用 static_cast,就会创建一个临时变量。 并且可以传递对此对象的引用,一切正常。
或者至少解决这个问题的我的同事是这么说的。
Regarding the second question: push_ref takes reference as a parameter, and you cannot have a reference to static const memeber of a class/struct. Once you call static_cast, a temporary variable is created. And a reference to this object can be passed, everything works just fine.
Or at least my colleague who resolved this said so.