未定义对静态类成员的引用


201

谁能解释为什么以下代码无法编译?至少在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;
}

我编辑了问题,使代码缩进了四个空格,而不是使用<pre> <code> </ code> </ pre>。这意味着尖括号不会被解释为HTML。
史蒂夫·杰索普

Answers:


199

您实际上需要在某个地方(在类定义之后)定义静态成员。试试这个:

class Foo { /* ... */ };

const int Foo::MEMBER;

int main() { /* ... */ }

那应该摆脱未定义的参考。


3
好的一点是,内联静态const整数初始化会创建一个带范围的整数常量,您不能接受其地址,而vector则需要一个引用参数。
伊万·特兰

10
该答案仅解决问题的第一部分。第二部分更有趣:为什么添加NOP强制转换使其无需外部声明即可工作?
nobar 2011年

32
我花了很多时间弄清楚,如果类定义在头文件中,则静态变量的分配应在实现文件中,而不是头中。
shanet 2012年

1
@shanet:非常好-我应该在回答中提到这一点!
德鲁·霍尔

但是,如果我将其声明为const,是否无法更改该变量的值?
Namratha

78

问题的出现是由于新的C ++功能与您要执行的操作发生了有趣的冲突。首先,让我们看一下push_back签名:

void push_back(const T&)

期望引用类型为object的对象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

但是没有预处理器宏的麻烦(并且具有类型安全性)。这意味着期望参考的向量不能获得一个。


2
谢谢,这有所帮助... 如果还不存在,就有资格获得stackoverflow.com/questions/1995113/strangest-language-feature ...
Andre Holzner 2010年

1
同样值得注意的是,MSVC接受了非广播版本,没有任何投诉。
porges

4
-1:这根本不是事实。当在某个地方使用过的静态成员时,仍然应该定义它们以内联方式初始化。编译器的优化可能会消除您的链接器错误,但不会改变它。在这种情况下,您的左值到右值转换(由于强制转换(int))将在转换单元中以完全可见的常量进行,并且Foo::MEMBER不再被使用。这与第一个函数调用相反,在第一个函数调用中,传递引用并在其他位置进行评估。
2014年

void push_back( const T& value );const&可以与右值绑定。
科斯塔斯

59

如果出于某种需要,C ++标准需要为您的静态const成员定义。

该定义是必需的,例如,如果使用了它的地址。 push_back通过const引用获取其参数,因此严格来说,编译器需要您成员的地址,并且需要在名称空间中对其进行定义。

当您显式转换常量时,您正在创建一个临时对象,并且这个临时对象绑定到了引用上(在标准中的特殊规则下)。

这是一个非常有趣的案例,我实际上认为值得提出一个问题,以便将std更改为对您的常数成员具有相同的行为!

虽然,这可能以一种奇怪的方式被视为一元'+'运算符的合法使用。基本上,结果unary +是一个右值,因此适用将右值绑定到const引用的规则,并且我们不使用静态const成员的地址:

v.push_back( +Foo::MEMBER );

3
+1。是的,对于类型为T的对象x,可以使用表达式“(T)x”来绑定const ref,而普通的“ x”不能绑定表达式,这确实很奇怪。我喜欢您对“一元+”的观察!谁会想到,可怜的小“一元+”实际上有用途... :)
j_random_hacker

3
考虑一般情况... C ++中是否还有其他类型的对象具有以下属性:(1)仅当已定义它时才可以将其用作左值,而(2)可以转换为右值而无需定义的?
j_random_hacker 2009年

这是一个很好的问题,至少目前我无法想到任何其他示例。这可能只是在这里,因为委员会大多只是在重用现有语法。
理查德·科登

@RichardCorden:一元+如何解决这个问题?
Blood-HaZaRd

1
@ Blood-HaZaRd:在右值引用之前,的唯一重载push_back是a const &。直接使用该成员会导致该成员绑定到引用,这需要它具有地址。但是,添加+会使用成员的值创建一个临时值。然后,引用将绑定到该临时对象,而不是要求成员具有地址。
理查德·科登

10

aa

class Aaa {

protected:

    static Aaa *defaultAaa;

};

氨基酸

// You must define an actual variable in your program for the static members of the classes

static Aaa *Aaa::defaultAaa;

1

不知道强制转换为什么起作用,但是直到第一次加载Foo时才分配Foo :: MEMBER,并且由于您从不加载它,所以也从未分配它。如果您在某处提到Foo,它可能会起作用。


我认为您正在回答自己的问题:强制转换有效,因为它创建了(临时)引用。
Jaap Versteegh

1

使用C ++ 11,上面的基本类型将成为可能

class Foo {
public:  
  static constexpr int MEMBER = 1;  
};

constexpr部分创建一个与静态变量相对的静态表达式 -行为就像一个非常简单的内联方法定义。不过,这种方法在模板类内部使用C字符串constexprs证明有点不稳定。


这对我来说至关重要,因为“ static const int MEMBER = 1;” 在开关中使用MEMBER是必需的,而在向量中使用它则需要外部声明,并且您不能同时拥有两者。但是,你给这里表达工作,两个,至少我的编译器。
Ben Farmer

0

关于第二个问题:push_ref将引用作为参数,并且您不能具有对类/结构的静态const成员的引用。调用static_cast后,将创建一个临时变量。可以传递对该对象的引用,一切正常。

或者至少我的同事解决了这个问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.