C ++でのグローバル定数の定義


81

いくつかのソースファイルで表示されるようにC ++で定数を定義したいと思います。ヘッダーファイルでそれを定義する次の方法を想像することができます:

  1. #define GLOBAL_CONST_VAR 0xFF
  2. int GLOBAL_CONST_VAR = 0xFF;
  3. 値を取得する関数(例int get_GLOBAL_CONST_VAR()
  4. enum { GLOBAL_CONST_VAR = 0xFF; }
  5. const int GLOBAL_CONST_VAR = 0xFF;
  6. extern const int GLOBAL_CONST_VAR; そして1つのソースファイルで const int GLOBAL_CONST_VAR = 0xFF;

オプション(1)-絶対に使用したいオプションではありません

オプション(2)-ヘッダーファイルを使用して各オブジェクトファイルの変数のインスタンスを定義する

オプション(3)-ほとんどの場合、IMOは過剰殺害です

オプション(4)-列挙型には具体的な型がないため、多くの場合、適切ではない可能性があります(C ++ 0Xは型を定義する可能性を追加します)

したがって、ほとんどの場合、(5)と(6)のどちらかを選択する必要があります。私の質問:

  1. (5)と(6)のどちらが好きですか?
  2. (5)は問題ないのに、(2)は問題ないのはなぜですか?

1
5対2:「const」は内部リンクを意味します。このバージョン5ヘッダーを複数の翻訳単位に含めると、「単一定義規則」に違反することはありません。また、constを使用すると、コンパイラは「定数畳み込み」を実行できますが、non-const変数の値は変更される可能性があります。オプション6は間違っています。外部リンケージを強制するには、cppファイルにも「extern」が必要です。そうしないと、リンカーエラーが発生します。オプション6には、値を非表示にするという利点があります。しかし、それはまた、定数畳み込みを不可能にします。
Sellibitze 2010

回答:


32

(5)あなたが言いたいことを正確に言います。さらに、コンパイラーはほとんどの場合それを最適化できます。(6)一方、コンパイラーは、最終的に変更するかどうかを知らないため、コンパイラーがそれを最適化することはできません。


1
OTOH、5は、ODRの違反として技術的に違法です。ただし、ほとんどのコンパイラはそれを無視します。
ジョエル

ええと、私はそれを何も定義していないと考えるのが好きです、私はちょうどコンパイラに数字にきれいな名前を付けるように言いました。すべての意図と目的において、それが(5)であり、実行時にオーバーヘッドがないことを意味します。
Blindy 2010

2
(5)ODRの違反ですか?そうである場合は、(6)が望ましいです。(6)の場合、なぜコンパイラは「変更するかどうかわからない」のですか?extern const int ...そしてconst int ...両方とも一定ではありませんか?
D.Shawley 2010

4
AFAIK、5)から6)の間、定数のタイプがintベースでない場合は6)のみが許可されます。
クライム2010

11
ODR違反はなく、定数オブジェクトはデフォルトで静的です。
avakar 2010

71

間違いなくオプション5を選択してください-それはタイプセーフであり、コンパイラーが最適化できるようにします(その変数のアドレスを取得しないでください:)また、ヘッダーにある場合は、グローバルスコープを汚染しないように名前空間に貼り付けます:

// header.hpp
namespace constants
{
    const int GLOBAL_CONST_VAR = 0xFF;
    // ... other related constants

} // namespace constants

// source.cpp - use it
#include <header.hpp>
int value = constants::GLOBAL_CONST_VAR;

3
header.hpp複数のソースファイルに含めようとすると、再定義エラーが発生します。
LRDPRDX 2017

なぜこれがまだ賛成されているのかはわかりません-ほぼ10年が経ちましたが、今日ではconstexpr、そのようなものの列挙を入力しました。
ニコライフェティソフ

24

(5)はGLOBAL_CONST_VAR、すべての変換単位で積分定数式(ICE)として定義されているため、(6)よりも「優れています」。たとえば、すべての変換単位で配列サイズおよび大文字と小文字のラベルとして使用できます。(6)の場合、GLOBAL_CONST_VARそれが定義されているその翻訳単位でのみ、定義のポイントの後でのみICEになります。他の翻訳ユニットでは、ICEとして機能しません。

ただし、(5)はGLOBAL_CONST_VAR内部リンケージを提供することに注意してください。つまり、の「アドレスID」はGLOBAL_CONST_VAR各変換ユニットで&GLOBAL_CONST_VAR異なります。つまり、は各変換ユニットで異なるポインタ値を提供します。ほとんどの使用例ではこれは問題ではありませんが、一貫したグローバルな「アドレスID」を持つ定数オブジェクトが必要な場合は、(6)を使用して、定数のICE性を犠牲にする必要があります。処理する。

また、定数のICE性が問題ではなく(整数型ではない)、型のサイズが大きくなる(スカラー型ではない)場合、通常、(6)は(5)よりも優れたアプローチになります。

(2)はGLOBAL_CONST_VARデフォルトで外部リンケージがあるため、(2)はOKではありません。ヘッダーファイルに入れると、通常、の定義が複数になりGLOBAL_CONST_VAR、エラーになります。constC ++のオブジェクトにはデフォルトで内部リンケージがあります。これが、(5)が機能する理由です(そして、前述のように、GLOBAL_CONST_VAR各変換ユニットで個別の独立したオブジェクトを取得するのはそのためです)。


C ++ 17以降、宣言するオプションがあります

inline extern const int GLOBAL_CONST_VAR = 0xFF;

ヘッダーファイル内。これにより、(方法(5)と同様に)すべての変換ユニットでICEが同時に提供され、グローバルアドレスIDが維持されGLOBAL_CONST_VARます。すべての変換ユニットで同じアドレスになります。


8

C ++ 11以降を使用している場合は、コンパイル時定数を使用してみてください。

constexpr int GLOBAL_CONST_VAR{ 0xff };

1
私見、これはこの問題に対する唯一の満足のいく解決策です。
lanoxx

5

それが定数になる場合は、定数としてマークする必要があります-それが私の意見では2が悪い理由です。

コンパイラーは、値のconstの性質を使用して、一部の数学、および実際に値を使用する他の操作を展開できます。

5と6-の間の選択-うーん; 5は私にとってちょうど気分が良くなります。

6)では、値はその宣言から不必要に切り離されています。

私は通常、定数などを定義するだけのこれらのヘッダーを1つ以上持っていて、他の「賢い」ものはありません。どこにでも簡単に含めることができる軽量のヘッダーです。


3
(6)は不必要な分離ではなく、意図的な選択です。大きな定数がたくさんある場合、(6)のように宣言しないと、実行可能ファイルで多くのスペースを浪費します。数学ライブラリで発生する可能性があります...無駄は100k未満の場合がありますが、それでも重要な場合があります。(一部のコンパイラには、これを回避する他の方法があります。MSVCには「once」属性などがあると思います。)
Dan Olson

(5)を使用すると、それがconstのままであるかどうかを本当に確信することはできません(常にconstnessを捨てることができます)。これが、私がまだ列挙型を好む理由です。
fmuecke 2010

@Dan Olson-それは非常に良い点です-私の答えは、ここに含まれる型がintであるという事実に基づいていました。しかし、より大きな値を扱う場合、extern宣言は確かにより良い計画です。
Andras Zoltan

@ fmuecke-はい、その通りです-その場合、列挙値はこれを防ぎます。しかし、それは私たちが常にこの方法で書き込みから値を保護する必要があることを意味しますか?プログラマーがコードを悪用したい場合、(target_type *)((void *)&value)キャストが大混乱を引き起こし、捕まえられないほど多くの領域があり、時にはそれらに信頼を置く必要があります。そして確かに私たち自身、そうではありませんか?
Andras Zoltan

@fmuecke constとして宣言されている変数は、プログラムによって変更することはできません(変更しようとすると、未定義の動作になります)。const_castは、元の変数がconstとして宣言されていない状況でのみ定義されます(たとえば、非const値をconst&として関数に渡す)。
デビッドストーン

5

2番目の質問に答えるには:

(2)単一定義規則に違反しているため、違法です。それがGLOBAL_CONST_VAR含まれるすべてのファイルで、つまり複数回定義されます。(5)は、単一定義規則の対象ではないため、合法です。それぞれGLOBAL_CONST_VARが個別の定義であり、ファイルが含まれているファイルに対してローカルです。もちろん、これらの定義はすべて同じ名前と値を共有しますが、アドレスが異なる可能性があります。


4

C ++ 17inline変数

この素晴らしい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アップストリーム

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

インライン変数に関する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 nm言うu

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

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

GCC 7.4.0、Ubuntu18.04でテスト済み。


2
const int GLOBAL_CONST_VAR = 0xFF;

定数だから!


1
また、マクロのように扱われないため、デバッグが容易になります。
kayleeFrye_onDeck 2017年

-1、これにより、ヘッダーを複数のソースファイルに含めると、再定義の警告/エラーが発生します。また、この答えはニコライフェティソフの答えの複製です。
lanoxx 2018

1

それはあなたの要件に依存します。(5)はほとんどの通常の使用法に最適ですが、多くの場合、すべてのオブジェクトファイルで常にストレージスペースを占有します。(6)それが重要な状況でこれを回避することができます。

(4)は、ストレージスペースが割り当てられないことを優先する場合にも適切な選択ですが、もちろん、整数定数に対してのみ機能します。


1
#define GLOBAL_CONST_VAR 0xFF // this is C code not C++
int GLOBAL_CONST_VAR = 0xFF; // it is not constant and maybe not compilled
Some function returing the value (e.g. int get_LOBAL_CONST_VAR()) // maybe but exists better desision
enum { LOBAL_CONST_VAR = 0xFF; } // not needed, endeed, for only one constant (enum elms is a simple int, but with secial enumeration)
const int GLOBAL_CONST_VAR = 0xFF; // it is the best
extern const int GLOBAL_CONST_VAR; //some compiller doesn't understand this
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.