211

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

9 回答 9

203

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

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

const int Foo::MEMBER;

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

那应该摆脱未定义的引用。

于 2008-11-07T17:57:35.213 回答
78

问题的出现是因为新的 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

但是没有预处理器宏的麻烦(并且具有类型安全性)。这意味着需要参考的向量无法获得参考。

于 2008-11-07T18:00:46.907 回答
62

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

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

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

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

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

v.push_back( +Foo::MEMBER );
于 2008-11-07T18:03:49.927 回答
10

啊啊啊

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;
于 2012-12-04T08:32:36.703 回答
4

在 C++17 中,使用inline变量有一个更简单的解决方案:

struct Foo{
    inline static int member;
};

这是 的定义member,而不仅仅是它的声明。与内联函数类似,不同翻译单元中的多个相同定义不会违反 ODR。不再需要为定义选择最喜欢的 .cpp 文件。

于 2021-03-10T17:20:13.810 回答
2

只是一些额外的信息:

C++ 允许将整型和枚举类型的 const 静态类型“定义”为类成员。但这实际上不是一个定义,只是一个“初始化标记”

您仍然应该在类之外编写您的成员的定义。

9.4.2/4 - 如果一个静态数据成员是 const 整数或 const 枚举类型,它在类定义中的声明可以指定一个常量初始化器,它应该是一个整数常量表达式 (5.19)。在这种情况下,成员可以出现在整型常量表达式中。如果在程序中使用该成员,则该成员仍应在名称空间范围内定义,并且名称空间范围定义不应包含初始值设定项。

于 2021-09-19T01:35:27.003 回答
1

不知道为什么演员表起作用,但是直到第一次加载 Foo 时才分配 Foo::MEMBER ,并且由于您从不加载它,因此永远不会分配它。如果您在某处引用了 Foo,它可能会起作用。

于 2008-11-07T18:01:53.783 回答
1

使用 C++11,以上对于基本类型是可能的

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

constexpr部分创建一个静态表达式,而不是一个静态变量——它的行为就像一个非常简单的内联方法定义。不过,在模板类中使用 C 字符串 constexprs 时,该方法被证明有点不稳定。

于 2016-06-20T07:12:30.527 回答
0

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

或者至少我解决这个问题的同事是这么说的。

于 2014-07-18T12:44:30.173 回答