web-dev-qa-db-ja.com

C ++ヘッダーで定数変数を定義する

私が取り組んでいるプログラムには、すべてのクラスに適用される多くの定数があります。 1つのヘッダーファイル「Constants.h」を作成し、関連するすべての定数を宣言できるようにします。その後、他のクラスに#include "Constants.hを含めることができます。

#ifndef ... #define ...構文を使用して正常に動作するようにしました。ただし、const int...形式の定数を使用することをお勧めします。方法はよくわかりません。

52
user1599559

ヘッダーファイルで一連の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++リンケージルールに精通していない人にとって読みやすいものです。

すべての定数がintである場合、使用できる別の方法は、識別子を列挙として宣言することです。

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

これらのメソッドはすべてヘッダーのみを使用し、宣言された名前をコンパイル時定数として使用できます。 extern const intと別個の実装ファイルを使用すると、名前がコンパイル時定数として使用されなくなります。


特定の定数を暗黙的に内部リンケージにするルールは、他のタイプの定数とまったく同じように、ポインターに適用されることに注意してくださいdoes。ただし、注意が必要なのは、ポインターをconstとしてマークするには、ほとんどの人が他の型の変数をconstにするために使用する構文が少し異なることです。あなたがする必要があります:

int * const ptr;

規則が適用されるように、定数ポインターを作成します。

また、これが一貫してint constの代わりにconst intの後にconstを置くことを好む理由の1つであることに注意してください。また、変数の横に*を配置します。つまり、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;
71
bames53

私はnamespaceがこの種の目的に適しています。

オプション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;

また、このタイプのHardCoded Const変数を保持するためにクラスを使用することはありません。

26
Manikanda raj S

通常は使用しないでください。ヘッダーファイルのconst int(複数のソースファイルに含まれている場合)。 global const変数は暗黙的に静的 であり、必要以上のメモリを消費するため、変数はソースファイル(技術的に言えば翻訳単位)ごとに1回定義されるためです。

代わりに、変数を実際に定義する特別なソースファイル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;
11

C++ 17 inline変数

この素晴らしいC++ 17機能により、次のことが可能になります。

  • 各定数に1つのメモリアドレスのみを使用すると便利です
  • constexprとして保存します: constexpr externの宣言方法
  • 1つのヘッダーから1行で実行します

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アップストリーム

参照: インライン変数はどのように機能しますか?

インライン変数のC++標準

C++標準では、アドレスが同じであることを保証しています。 C++ 17 N4659標準ドラフト 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 nmuについて述べています:

"u"シンボルは一意のグローバルシンボルです。これはGNU ELFシンボルバインディングの標準セットの拡張です。このようなシンボルの場合、ダイナミックリンカーは、プロセス全体でこの名前とタイプのシンボルが1つだけ使用されていることを確認します。

そのため、専用のELF拡張機能があることがわかります。

「グローバル」C++ 17標準ドラフトconststaticを意味する

これは、 https://stackoverflow.com/a/12043198/895245 で言及された内容の引用です。

C++ 17 n4659標準ドラフト 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オブジェクトを配置できます。

元の機能への影響:明確に定義された機能のセマンティクスへの変更。

変換の難しさ:意味変換。

広く使われている:めったにありません。

以下も参照してください: なぜconstはC++ではないのにC++で内部リンケージを意味するのですか?

GCC 7.4.0、Ubuntu 18.04でテスト済み。

多数のグローバル変数を作成するのではなく、多数のパブリックな静的定数を持つクラスを作成することを検討できます。まだグローバルですが、このようにクラスにラップされているので、定数がどこから来ているのか、そして定数であるはずです。

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;
}
1

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 *'に変換することを禁止している」ためです。 2番目のconstは、定数の複数の定義による定数のリンクエラーを回避するために必要です。 constsの一方または両方を省略してもコンパイラーは文句を言わないかもしれませんが、ソースコードの移植性は低下します。

0
Louis Strous