在C++中,在类作用域内声明/定义常量应该放在哪里?

78

我很好奇在C++中不同的常量声明和定义选项有哪些好处/坏处。最长一段时间里,我一直是在类定义之前在头文件的顶部声明它们:

//.h
const int MyConst = 10;
const string MyStrConst = "String";
class MyClass {
...
};

虽然这样会污染全局命名空间(我知道这是不好的事情,但从未找到过一个详细的理由列表说明为什么这是不好的),但是这些常量仍将作用于各自的翻译单元,所以没有包含此头文件的文件将无法访问这些常量。但是如果其他类定义了相同名称的常量,可能会产生名称冲突,这有可能是一个好的指示需要进行重构的领域。

最近,我决定更好的做法是在类定义中声明特定于类的常量:

//.h
class MyClass {
    public:
         static const int MyConst = 10;
...
    private:
         static const string MyStrConst;
...
};
//.cpp
const string MyClass::MyStrConst = "String";
常量的可见性将取决于常量是仅在类内部使用,还是需要供使用该类的其他对象使用。目前我认为这是最好的选择,主要是因为您可以将内部类常量保持为类私有,任何使用公共常量的其他类都将具有更详细的常量源引用(例如MyClass :: MyConst)。它也不会污染全局命名空间。尽管它需要在cpp文件中进行非整数初始化,但这也是它的缺点。
我还考虑过将常量移动到它们自己的头文件中,并在其中添加一个命名空间,以防其他类需要这些常量,但不需要整个类定义。
只是寻求意见和可能尚未考虑的其他选项。
7个回答

51

您声称将非整数常量声明为静态类成员"会导致在cpp文件中需要进行非整数初始化"并不完全正确。它确实需要在cpp文件中进行定义,但这不是一个"劣势",而是取决于您的意图。在C ++中,命名空间级别的const对象默认具有内部链接,这意味着在您最初的变体中声明。

const string MyStrConst = "String"; 

等同于

static const string MyStrConst = "String"; 

也就是说,它将在每个包含此头文件的翻译单元中定义一个独立的MyStrConst对象。您是否意识到了这一点?这是您的意图还是不是?

无论如何,如果您不需要在每个翻译单元中具有单独的对象,那么在您原始示例中声明MyStrConst常量并不是一个好的做法。通常,您只需在头文件中放置一个非定义声明即可。

extern const string MyStrConst; 

并在 cpp 文件中提供一个定义。

const string MyStrConst = "String";
因此,确保整个程序使用相同的常量对象。换句话说,对于非整型常量,通常的做法是在cpp文件中定义它们。因此,无论您如何声明它(在类内或外部),通常都要处理“劣势”,即必须在cpp文件中定义它。当然,正如我上面所说,对于命名空间常量,您可以使用第一个变体中的内容来逃避,但那只是“懒惰编码”的一个例子。
无论如何,我认为没有理由过度复杂化问题:如果该常量与类有明显的“关联”,则应将其声明为类成员。
附言:访问限定符(public,protected,private)不控制名称的可见性。它们仅控制其可访问性。名称始终可见。

请问可见性和可访问性有什么区别?我认为它们是一样的,你能举个例子吗? - toolchainX
@toolchainX: 例如:一个成员函数可能是私有的,但是在编译器中该函数可以被“看到”,这意味着我们假设定义存在并且编译器可以“看到”它。编译器只是强制执行无法访问[编译器错误]。删除定义或将定义放在某个“隐藏”的地方会使得该函数在这些条件下不可见,即使对于成员函数也是如此,而不考虑访问权限[链接错误]。 - wardw
把它放在 .cpp 文件中会有损,因为编译器无法内联常量,因为它不在编译器的翻译单元中。如果该常量只是一个包装整数的类,则最好将其放在头文件中,以便编译器知道它在所有编译单元中的值。 - qbt937

10

全局命名空间的污染是不好的,因为有人(例如你使用的库的作者)可能想要使用名称MyConst来完成其他任务。这可能会导致严重的问题(无法同时使用的库等)。

如果常量与单个类相关联,则第二种解决方案显然是最佳的。如果这不太容易(考虑物理或数学常数,它们在程序中没有任何类别),则命名空间解决方案比该方法更好。顺便说一下:如果你必须兼容旧的C++编译器,请记住其中一些编译器不能在头文件中使用整数初始化 - 在这种情况下,您必须在C++文件中进行初始化或者使用旧的enum技巧。

我认为对于常量来说,目前没有更好的选择了...至少我暂时想不到...


5

污染全局命名空间显然是不好的。如果我包含一个头文件,我不希望在该头文件声明的常量中遇到或调试名称冲突。这些类型的错误非常令人沮丧,有时很难诊断。例如,我曾经链接到一个项目,其中一个头文件中定义了以下内容:

#define read _read

如果你的常量是命名空间污染,那么这就是命名空间核废料。这种情况的表现是一系列非常奇怪的编译器错误,抱怨缺少_read函数,但仅在链接该库时才会出现。我们最终将read函数重命名为其他名称,这并不难,但应该是不必要的。
你的第二个解决方案非常合理,因为它将变量放入了作用域中。这与类没有关联,如果我需要在类之间共享常量,我会在它们自己的命名空间和头文件中声明常量。这对于编译时来说并不好,但有时是必要的。
我还见过有人将常量放入自己的类中,这可以实现为单例模式。对我来说,这似乎是得不偿失的,语言为您提供了一些声明常量的工具。

2
如果只有一个类将使用这些常量,请在类主体内声明它们为static const。如果一堆相关的类将使用这些常量,请将它们声明在仅包含常量和实用方法的类/结构体内部,或者在专用命名空间内部声明。例如,
namespace MyAppAudioConstants
{
     //declare constants here
}

如果它们是整个应用程序(或其大部分)使用的常量,请在一个命名空间内声明它们,并将其放置在一个头文件中,该头文件被隐式或显式地包含在所有地方。
namespace MyAppGlobalConstants
{
    //declare constants here
}

2

如果它们未在头文件中被引用,您可以将它们声明为全局变量。 在C++文件中这样做,然后它们是私有的,不会污染全局命名空间。


是的,但在这种情况下,您必须使用static或匿名命名空间 - 如果不这样做,在某些实现中,您会污染链接器使用的全局命名空间,并在链接时出现名称冲突... - hjhill

2

就个人而言,我采用第二种方法;已经使用了好几年了,对我来说效果很好。

从可见性的角度来看,我倾向于将私有常量文件级别设置为静态变量,因为没有在实现文件之外的人需要知道它们的存在;这有助于防止链式反应重新编译,如果您需要更改它们的名称或添加新的常量,则它们的名称范围与使用范围相同...


-2

不要污染全局命名空间,而应该污染局部的命名空间。

namespace Space
  {
  const int Pint;
  class Class {};
  };

但实际上...

class Class
  {
  static int Bar() {return 357;}
  };

这不太清楚。Pint常量是什么意思,为什么Bar()返回375 - Paul Wintz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接