在C ++标头中定义常量变量

时间:2012-08-20 17:53:35

标签: c++ header constants

我正在处理的程序中有许多适用于所有类的常量。我想制作一个头文件“Constants.h”,并能够声明所有相关的常量。然后在我的其他课程中,我可以包括#include "Constants.h

我使用#ifndef ... #define ...语法使其工作正常。但是,我更喜欢使用const int...形式的常量。我不太清楚如何。

6 个答案:

答案 0 :(得分:65)

您只需在标头文件中定义一系列const ints

// Constants.h
#if !defined(MYLIB_CONSTANTS_H)
#define MYLIB_CONSTANTS_H 1

const int a = 100;
const int b = 0x7f;

#endif

这是有效的,因为在C ++中,名称空间范围(包括全局命名空间)的名称显式声明为const而未显式声明为extern具有内部链接,因此当您将翻译单元链接在一起时,这些变量不会导致重复的符号。或者,您可以将常量显式声明为静态。

static const int a = 100;
static const int b = 0x7f;

这与C更兼容,对于可能不熟悉C ++链接规则的人来说更具可读性。

如果所有常量都是整数,则可以使用的另一种方法是将标识符声明为枚举。

enum mylib_constants {
    a = 100;
    b = 0x7f;
};

所有这些方法仅使用标头,并允许声明的名称用作编译时常量。使用extern const int和单独的实现文件可以防止名称被用作编译时常量。


请注意,使某些常量隐式内部链接的规则适用于指针,与其他类型的常量完全相同。但棘手的是,将指针标记为const需要的语法与大多数人用来制作其他类型const变量的语法略有不同。你需要这样做:

int * const ptr;

制作一个常量指针,以便规则适用于它。

另请注意,这是我更喜欢在const类型而不是int const之后始终放置const int的原因之一。我还将*放在变量旁边:ie int *ptr;而不是int* ptr;

我喜欢做这些事情,因为它们反映了C ++真正起作用的一般情况。替代方案(const intint* p)只是特别容易使一些简单的东西更具可读性。问题在于,当你走出这些简单的案例时,特殊的套装替代品会变得有误导性。

因此,虽然前面的例子显示了const的常见用法,但实际上我建议人们这样写:

int const a = 100;
int const b = 0x7f;

static int const a = 100;
static int const b = 0x7f;

答案 1 :(得分:25)

我更喜欢名称空间以达到这种目的。

选项1:

#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H

//  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
namespace LibConstants
{
  const int CurlTimeOut = 0xFF;     // Just some example
  ...
}
#endif

// source.cpp
#include <LibConstants.hpp>
int value = LibConstants::CurlTimeOut;

选项2:

#ifndef MYLIB_CONSTANTS_H
#define MYLIB_CONSTANTS_H
//  File Name : LibConstants.hpp    Purpose : Global Constants for Lib Utils
namespace CurlConstants
{
  const int CurlTimeOut = 0xFF;     // Just some example
  ...
}

namespace MySQLConstants
{
  const int DBPoolSize = 0xFF;      // Just some example
  ...
}
#endif



// source.cpp
#include <LibConstants.hpp>
int value = CurlConstants::CurlTimeOut;
int val2  = MySQLConstants::DBPoolSize;

我永远不会使用Class来保存这种类型的HardCoded Const变量。

答案 2 :(得分:10)

您通常不应使用例如头文件中的const int,如果它包含在多个源文件中。那是因为每个源文件(从技术上讲,翻译单位)将定义一次变量,因为global const variables are implicitly static占用的内存超过了所需的内存。

您应该有一个特殊的源文件,Constants.cpp实际定义变量,然后在头文件中将变量声明为extern

像这个头文件:

// Protect against multiple inclusions in the same source file
#ifndef CONSTANTS_H
#define CONSTANTS_H

extern const int CONSTANT_1;

#endif

这是在源文件中:

const int CONSTANT_1 = 123;

答案 3 :(得分:1)

您可以考虑创建一个具有大量公共静态常量的类,而不是制作一堆全局变量。它仍然是全局的,但是这种方式它包含在一个类中,所以你知道常量来自哪里,它应该是一个常数。

Constants.h

#ifndef CONSTANTS_H
#define CONSTANTS_H

class GlobalConstants {
  public:
    static const int myConstant;
    static const int myOtherConstant;
};

#endif

Constants.cpp

#include "Constants.h"

const int GlobalConstants::myConstant = 1;
const int GlobalConstants::myOtherConstant = 3;

然后你可以像这样使用它:

#include "Constants.h"

void foo() {
  int foo = GlobalConstants::myConstant;
}

答案 4 :(得分:0)

看来bames53的答案可以扩展到在名称空间和类声明中定义整数和非整数常量值,即使它们包含在多个源文件中也是如此。不必将声明放在头文件中,而将定义放在源文件中。以下示例适用于Microsoft Visual Studio 2015,适用于OS / 390的z / OS V2.2 XL C / C ++和适用于GNU / Linux 4.16.14(Fedora 28)的g ++(GCC)8.1.1 20180502。请注意,常量仅在包含在多个源文件中的单个头文件中声明/定义。

在foo.cc中:

#include <cstdio>               // for puts

#include "messages.hh"
#include "bar.hh"
#include "zoo.hh"

int main(int argc, const char* argv[])
{
  puts("Hello!");
  bar();
  zoo();
  puts(Message::third);
  return 0;
}

在messages.hh中:

#ifndef MESSAGES_HH
#define MESSAGES_HH

namespace Message {
  char const * const first = "Yes, this is the first message!";
  char const * const second = "This is the second message.";
  char const * const third = "Message #3.";
};

#endif

在bar.cc中:

#include "messages.hh"
#include <cstdio>

void bar(void)
{
  puts("Wow!");
  printf("bar: %s\n", Message::first);
}

在zoo.cc中:

#include <cstdio>
#include "messages.hh"

void zoo(void)
{
  printf("zoo: %s\n", Message::second);
}

在bar.hh中:

#ifndef BAR_HH
#define BAR_HH

#include "messages.hh"

void bar(void);

#endif

在zoo.hh:

#ifndef ZOO_HH
#define ZOO_HH

#include "messages.hh"

void zoo(void);

#endif

这将产生以下输出:

Hello!
Wow!
bar: Yes, this is the first message!
zoo: This is the second message.
Message #3.

数据类型char const * const表示指向常量字符数组的常量指针。需要第一个const,因为(根据g ++)“ ISO C ++禁止将字符串常量转换为'char *'”。需要第二个const来避免由于常量定义(然后常量不足)而导致的链接错误。如果省略const中的一个或两个,编译器可能不会抱怨,但是源代码的可移植性较差。

答案 5 :(得分:0)

C ++ 17 inline变量

这项令人敬畏的C ++ 17功能使我们能够:

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

编译并运行:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream

另请参阅:How do inline variables work?

内联变量的C ++标准

C ++标准保证地址相同。 C++17 N4659 standard draft 10.1.6“内联说明符”:

  

6具有外部链接的内联函数或变量在所有翻译单元中应具有相同的地址。

cppreference https://en.cppreference.com/w/cpp/language/inline解释说,如果未提供static,则它具有外部链接。

内联变量实现

我们可以观察到它是如何实现的:

nm main.o notmain.o

其中包含:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

man nm说说u

  

“ u”该符号是唯一的全局符号。这是对ELF符号绑定的标准集合的GNU扩展。对于这样的符号,动态链接器将确保在整个过程中                  只有一个使用此名称和类型的符号。

所以我们看到有一个专用的ELF扩展。

const上的C ++ 17标准草案暗示static

这是在https://stackoverflow.com/a/12043198/895245

中提到的内容的报价。

C++17 n4659 standard draft 6.5“程序和链接”:

  

3   具有名称空间范围(6.3.6)的名称如果是

的名称,则具有内部链接      
      
  • (3.1)—显式声明为静态的变量,函数或函数模板;或者,
  •   
  • (3.2)—既不是显式声明为extern也不是非易失性const限定类型的非内联变量   先前宣布具有外部联系;或
  •   
  • (3.3)-匿名联合的数据成员。
  •   

附录C(信息性)兼容性,C.1.2  第6条:“基本概念”提供了将其从C:更改的理由:

  

6.5 [也是10.1.7]

     

更改:显式声明为const而不显式声明为extern的文件范围的名称具有   内部链接,而在C语言中则具有外部链接。

     

合理性:由于const对象可以在C ++中的转换过程中用作值,因此该功能建议   程序员为每个const对象提供一个显式的初始化程序。该功能允许用户放置   源文件中包含多个翻译单元的const对象。

     

对原始功能的影响:更改了定义良好的功能的语义。

     

转换困难:语义转换。

     

广泛使用:很少。

另请参阅:Why does const imply internal linkage in C++, when it doesn't in C?