【问题标题】:Defining static const integer members in class definition在类定义中定义静态 const 整数成员
【发布时间】:2011-03-02 20:16:33
【问题描述】:

我的理解是 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。

【问题讨论】:

  • 在 Visual Studio 2010 上运行良好。
  • 这个确切的错误在gcc.gnu.org/wiki/…进行了解释
  • 这个问题表明 C++ 对“不要将#defines 用于常量”的回答仍然很差。
  • @JohannesOvermann 在这方面,我想提一下自 C++17 inline const int N = 10 以来对全局变量使用内联,据我所知,它仍然有一个由链接器定义的存储空间。在这种情况下,也可以使用关键字 inline 在类定义测试中提供静态变量 definition
  • 如何在另一个类中使用静态 const int 变量?

标签: c++ static declaration definition


【解决方案1】:

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

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

有趣的是,如果我注释掉对 std::min 的调用,代码编译和链接就好了(尽管前一行也引用了 test::N)。

知道发生了什么吗?

std::min 通过 const 引用获取其参数。如果按值取值,则不会有此问题,但是由于需要参考,因此还需要定义。

这是章节/诗句:

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

有关可能的解决方法,请参阅 Chu 的回答。

【讨论】:

  • 我明白了,这很有趣。在这种情况下,在声明点提供值与在定义点提供值有什么区别?推荐哪一个?
  • 好吧,我相信只要你从不真正“使用”变量,你就可以在没有定义的情况下逃脱。如果仅将其用作常量表达式的一部分,则永远不会使用该变量。否则,除了能够看到标题中的值之外,似乎没有太大的区别——这可能是也可能不是你想要的。
  • 简洁的答案是 static const x=1;是右值但不是左值。该值在编译时可用作常量(您可以使用它来标注数组) static const y; [无初始值设定项] 必须在 cpp 文件中定义,并且可以用作右值或左值。
  • 如果他们能扩展/改进这个就好了。在我看来,初始化但未定义的对象应该被视为与文字相同。例如,我们可以将文字 5 绑定到 const int&amp;。那么为什么不将 OP 的 test::N 视为相应的文字呢?
  • 有趣的解释,谢谢!这意味着在 C++ 中 static const int 仍然不能替代整数#defines。 enum 始终只是有符号整数,因此必须对单个常量使用枚举类。对我来说,将带有常量和已知值的常量声明退化为文字常量是很明显的,这样编译时不会出现问题。 C++还有很长的路要走...
【解决方案2】:

Bjarne Stroustrup 的示例in his 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 函数会在幕后以某种方式调用地址。

【讨论】:

  • std::min 通过引用获取其参数,这就是需要定义的原因。
  • 如果 AE 是模板类 AE 并且 c7 不是 int 而是 T::size_type,我将如何编写定义?我在标题中将值初始化为“-1”,但 clang 说未定义的值,我不知道如何编写定义。
  • @Fabian 我正在旅行,正在打电话,有点忙……但我认为您的评论听起来最好写成一个新问题。写一个MCVE 包括你得到的错误,也可能抛出 gcc 所说的内容。我敢打赌人们会很快告诉你这是什么。
  • @HostileFork:在编写 MCVE 时,您有时会自己找出解决方案。对于我来说,答案是template&lt;class K, class V, class C&gt; const typename AE&lt;K,V,C&gt;::KeyContainer::size_type AE&lt;K,V,C&gt;::c7;,其中 KeyContainer 是 std::vector 的 typedef。必须列出所有模板参数并写 typename 因为它是一种依赖类型。也许有人会发现这个评论很有用。但是,现在我想知道如何将其导出到 DLL 中,因为模板类当然是在标题中。需要导出c7???
【解决方案3】:

对于整数类型,另一种方法是将常量定义为类中的枚举:

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

【讨论】:

  • 这可能会解决问题。当 N 用作 min() 的参数时,它将导致创建一个临时变量,而不是尝试引用一个假定存在的变量。
  • 这样做的好处是可以设为私有。
【解决方案4】:

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

class classname
{
    public:
       static int const N;
}

在 .h 文件中,你必须有:

int const classname::N = 10;

在 .cpp 文件中。

【讨论】:

  • 我知道您可以在类声明中声明任何类型的变量。我说我认为静态整数常量也可以在类声明中定义。不是这样吗?如果不是,为什么编译器在我尝试在类中定义它的那一行没有给出错误?此外,为什么 std::cout 行不会导致链接器错误,而 std::min 行会导致?
  • 不,不能在类声明中定义静态成员,因为初始化会发出代码。与也发出代码的内联函数不同,静态定义是全局唯一的。
  • @HighCommander4:您可以为类定义中的static const 整数成员提供初始化程序。但这仍然没有定义该成员。有关详细信息,请参阅 Noah Roberts 的答案。
【解决方案5】:

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

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

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

【讨论】:

  • 甚至std::min(9, +test::N);
  • 这是一个大问题:这一切都是最优的吗?我不了解你们,但我对跳过定义的最大吸引力在于它在使用 const static 时不应该占用内存和开销。
【解决方案6】:

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

static constexpr int N = 10;

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

【讨论】:

  • 如果您需要像示例中那样将值作为“const int&”类型的参数传递怎么办? :-)
  • 效果很好。您不是 instantiating N 那样,只是将 const 引用传递给临时对象。 wandbox.org/permlink/JWeyXwrVRvsn9cBj
  • 可能是 C++17,而不是 C++14,甚至在早期版本的 gcc 6.3.0 及更低版本中也不是 C++17,这不是标准的东西。但感谢您提及这一点。
  • 啊,是的,你是对的。我没有在 wandbox 上尝试 c++14。哦,那是我说的“这在理论上仍然需要你定义常数”的部分。所以,你说得对,它不是“标准的”。
【解决方案7】:

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

不,3.1 §2 说:

声明是一个定义,除非它声明了一个函数而不指定函数的主体 (8.4),它包含 extern 说明符 (7.1.1) 或链接规范 (7.5),并且既不包含初始化器也不是函数体,它在类定义中声明一个静态数据成员(9.4),它是一个类名声明(9.1),它是一个不透明枚举声明(7.2),或者它是 typedef 声明 (7.1.3)、using-declaration (7.3.3) 或 using-directive (7.3.4)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-11
    • 1970-01-01
    • 2014-02-17
    • 1970-01-01
    • 2011-04-01
    相关资源
    最近更新 更多