在类定义中定义静态const整数成员


108

我的理解是,C ++允许在类内部定义静态const成员,只要它是整数类型即可。

那么,为什么以下代码给我一个链接器错误?

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

我得到的错误是:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

有趣的是,如果我注释掉对std :: min的调用,则代码可以编译并很好地链接(即使在上一行中也引用了test :: N)。

关于发生了什么的任何想法?

我的编译器是Linux上的gcc 4.4。


3
在Visual Studio 2010
Puppy

4
这个确切的错误在gcc.gnu.org/wiki/
Jonathan Wakely

在特定情况下char,您可以将其定义为constexpr static const char &N = "n"[0];。注意&。我猜这可行,因为文字字符串是自动定义的。不过,我对此有点担心-在不同翻译单元之间的头文件中,它的行为可能会很奇怪,因为字符串可能位于多个不同的地址。
亚伦·麦克戴德

1
这个问题表明C ++对“不对常量使用#defines”的答案仍然很差。
约翰尼斯·奥弗曼

1
@JohannesOvermann在这方面,我想提到从C ++ 17开始对全局变量使用内联的方法,据inline const int N = 10我所知,它仍然在链接器定义的位置存储。在这种情况下,也可以使用关键字内联在类定义测试中提供静态变量定义
Wormer

Answers:


72

我的理解是,C ++允许在类内部定义静态const成员,只要它是整数类型即可。

你是对的。您可以在类声明中初始化静态const积分,但这不是定义。

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

有趣的是,如果我注释掉对std :: min的调用,则代码可以编译并很好地链接(即使在上一行中也引用了test :: N)。

关于发生了什么的任何想法?

std :: min通过const引用获取其参数。如果将它们视为价值,那么您就不会有此问题,但是由于需要参考,因此也需要定义。

这是本章/节:

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

请参阅Chu的答案以获取可能的解决方法。


我知道,这很有趣。在这种情况下,在声明时提供值与在定义时提供值之间有什么区别?推荐哪一个?
HighCommander4 2010年

好吧,我相信,只要您从未真正“使用”变量,就可以不用定义就可以摆脱困境。如果仅将其用作常量表达式的一部分,则永远不会使用该变量。否则,除了能够看到标头中的值外,似乎没有太大的区别-可能不是您想要的。
爱德华·斯特朗奇

2
简洁的答案是静态const x = 1; 是一个右值,但不是一个左值。该值在编译时可以作为常量使用(可以使用数组标注维数)。[no initializer]必须在cpp文件中定义,并且可以用作右值或左值。
戴尔·威尔逊

2
如果他们可以扩展/改善这一点,那就太好了。我认为,初始化但未定义的对象应与文字相同。例如,允许我们将文字绑定5const int&。那么,为什么不将OP test::N当作相应的文字呢?
亚伦·麦克戴德

有趣的解释,谢谢!这意味着在C ++中,static const int仍不能替代整数#defines。枚举始终始终仅是带符号的int,因此必须对单个常量使用枚举类。对我来说,很明显的是将具有常量的声明简化为常量,并将值知道为文字常量,这样就可以毫无问题地进行编译。C ++还有很长的路要走……
Johannes Overmann

51

Bjarne Stroustrup 在其C ++ FAQ中的示例表明您是正确的,并且只有在使用地址时才需要定义。

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

他说:“(只有在静态成员具有超出定义的范围时,您才能使用该静态成员的地址”)。这表明它可以正常工作。也许您的min函数在后台调用了地址。


2
std::min通过引用获取其参数,这就是为什么需要定义的原因。
Rakete1111 '17

如果AE是模板类AE <class T>并且c7不是int而是T :: size_type,我将如何写定义?我在标头中将值初始化为“ -1”,但是clang表示未定义的值,我不知道如何编写定义。
Fabian

@Fabian我正在旅行,正在打电话,有点忙...但是我认为您的留言听起来最好写成一个新问题。写出包含您得到的错误的MCVE,也可能会抛出gcc所说的内容。我敢打赌,人们会很快告诉你这是什么。
HostileFork说不要相信

@HostileFork:编写MCVE时,有时您自己想出解决方案。就我而言,答案是template<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;KeyContainer是std :: vector <K>的typedef。必须列出所有模板参数并写入typename,因为它是依赖类型。也许有人会觉得此评论有用。但是,现在我想知道如何在DLL中导出它,因为模板类当然在标头中。我需要导出c7吗???
Fabian

24

无论如何,对于整数类型,执行此操作的另一种方法是将常量定义为类中的枚举:

class test
{
public:
    enum { N = 10 };
};

2
这可能会解决问题。当N用作min()的参数时,它将导致创建一个临时变量,而不是尝试引用一个假定存在的变量。
Edward Strange 2010年

这样做的好处是可以将其私有化。
Agostino '18

11

不只是int的。但是您不能在类声明中定义值。如果你有:

class classname
{
    public:
       static int const N;
}

在.h文件中,则您必须具有:

int const classname::N = 10;

在.cpp文件中。


2
我知道,你可以声明任何类型的类声明中的变量。我说过,我认为静态整数常量也可以在类声明中定义。不是吗?如果不是,为什么在我试图在类内定义它的那一行,编译器没有给出错误?此外,为什么std :: cout行不会导致链接器错误,而std :: min行会导致链接器错误?
HighCommander4 2010年

不可以,因为初始化会发出代码,所以无法在类声明中定义静态成员。与也发出代码的内联函数不同,静态定义是全局唯一的。
Amardeep AC9MF 2010年

@ HighCommander4:您可以为static const类定义中的整数成员提供初始化程序。但这仍未定义该成员。有关详细信息,请参见Noah Roberts答案。
AnT

9

这是解决该问题的另一种方法:

std::min(9, int(test::N));

(我认为Crazy Eddie的回答正确地描述了问题存在的原因。)


5
甚至是std::min(9, +test::N);
Tomilov Anatoliy 2013年

但是,这是一个大问题:这一切是否最优?我不了解你们,但是跳过定义的最大吸引力在于,使用const static时,它应该不占用任何内存,也不需要任何开销。
Opux

6

从C ++ 11开始,您可以使用:

static constexpr int N = 10;

从理论上讲,这仍然需要您在.cpp文件中定义常量,但是只要您不使用其地址N,任何编译器实现都不太可能产生错误;)。


而且,如果您需要像示例中那样将值作为'const int&'类型的参数传递,该怎么办?:-)
Wormer

很好 您不是以这种方式实例化 N,而只是将const引用传递给临时实例wandbox.org/permlink/JWeyXwrVRvsn9cBj
卡罗·伍德

C ++ 17可能不是C ++ 14,甚至不是早期版本的gcc 6.3.0和更低版本中的C ++ 17,这也不是标准的事情。但是感谢您提到这一点。
Wormer

嗯,是的。我没有在wandbox上尝试c ++ 14。哦,那是我说过的部分,“从理论上讲,这仍然需要您定义常量”。因此,您认为它不是“标准”是正确的。
卡罗·伍德

3

C ++允许在类内部定义静态const成员

不,3.1§2说:

声明是一个定义,除非它在未指定函数主体的情况下声明函数(8.4),它包含外部说明符(7.1.1)或链接规范(7.5),并且既没有初始化程序也不是函数体,而是声明了静态数据类定义(9.4)中的成员,它是类名称声明(9.1),它是不透明枚举声明(7.2),或者是typedef声明(7.1.3),使用声明(7.3。 3)或使用指令(7.3.4)。

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.