私が取り組んでいるプログラムには、すべてのクラスに適用される多くの定数があります。1つのヘッダーファイル「Constants.h」を作成し、関連するすべての定数を宣言できるようにします。次に、他のクラスに、を含めることができます#include "Constants.h
。
#ifndef
...#define ...
構文を使用して正常に動作するようになりました。const int...
ただし、定数の形式を使用したいと思います。どうすればいいのかよくわかりません。
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
すると、名前がコンパイル時定数として使用されなくなります。
特定の定数を暗黙的に内部リンケージにする規則は、他のタイプの定数とまったく同じように、ポインターに適用されることに注意してください。ただし、注意が必要なのは、ポインタをとしてマークconst
する場合、他のタイプの変数をconstにするためにほとんどの人が使用する構文とは少し異なる構文が必要になることです。あなたがする必要があります:
int * const ptr;
ルールが適用されるように、定数ポインタを作成します。
const
また、これが私が一貫してタイプの後に置くことを好む理由の1つであることに注意してください:int const
の代わりにconst int
。また*
、変数の横に次のように配置します。つまり、int *ptr;
代わりに(このint* ptr;
説明も比較してください)。
これらはC++が実際にどのように機能するかという一般的なケースを反映しているため、私はこの種のことを行うのが好きです。選択肢(const int
、int* p
)は、いくつかの単純なものを読みやすくするために特別な場合に使用されます。問題は、これらの単純なケースから抜け出すと、特別なケースの代替案が積極的に誤解を招くようになることです。
したがって、前の例はの一般的な使用法を示していますがconst
、実際には次のように書くことをお勧めします。
int const a = 100;
int const b = 0x7f;
と
static int const a = 100;
static int const b = 0x7f;
この種の目的には、名前空間の方が好きです。
オプション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;
そして、このタイプのHardCodedConst変数を保持するためにクラスを使用することは決してありません。
C++17inline
変数
この素晴らしいC++17機能により、次のことが可能になります。
constexpr
:constexpr externを宣言する方法は?main.cpp
#include <cassert>
#include "notmain.hpp"
int main() {
// Both files see the same memory address.
assert(¬main_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 ¬main_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
インライン変数に関するC++標準
C ++標準は、アドレスが同じになることを保証します。C ++ 17 N4659標準ドラフト 10.1.6「インライン指定子」:
6インライン関数または外部リンケージを持つ変数は、すべての変換単位で同じアドレスを持つ必要があります。
cppreference https://en.cppreference.com/w/cpp/language/inlinestatic
は、指定されていない場合、外部リンクがあることを説明しています。
インライン変数の実装
それがどのように実装されているかを観察することができます:
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拡張です。このようなシンボルの場合、ダイナミックリンカーは、プロセス全体で、この名前とタイプのシンボルが1つだけ使用されていることを確認します。
したがって、このための専用のELF拡張機能があることがわかります。
「グローバル」に関するC++17標準ドラフトconst
はstatic
これは、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オブジェクトを配置できます。
元の機能への影響:明確に定義された機能のセマンティクスに変更します。
変換の難しさ:意味変換。
広く使用されている方法:めったにありません。
参照:CではないのにconstがC ++で内部リンクを意味するのはなぜですか?
GCC 7.4.0、Ubuntu18.04でテスト済み。
const int
複数のソースファイルに含まれている場合は、通常、ヘッダーファイルなどで使用しないでください。これは、グローバル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;
一連のグローバル変数を作成するのではなく、一連のパブリック静的定数を持つクラスを作成することを検討してください。それはまだグローバルですが、このようにクラスにラップされているので、定数がどこから来ているのか、そしてそれが定数であるはずであることがわかります。
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;
}
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.120180502で機能します。定数は、複数のソースファイルに含まれる単一のヘッダーファイルでのみ宣言/定義されることに注意してください。
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 ++によると)「ISOC++は文字列定数を'char *'に変換することを禁じている」ため、最初のものが必要です。2つ目const
は、(定数が不十分な)定数の複数の定義によるリンクエラーを回避するために必要です。の一方または両方を省略してもコンパイラは文句を言わないかもしれませんconst
が、ソースコードの移植性は低くなります。