クラス定義での静的const整数メンバーの定義


109

私の理解では、C ++では、静的なconstメンバーが整数型である限り、クラス内で定義できます。

では、次のコードでリンカエラーが発生するのはなぜですか。

#include <algorithm>
#include <iostream>

class test
{
public:
    static const int N = 10;
};

int main()
{
    std::cout << test::N << "\n";
    std::min(9, test::N);
}

私が得るエラーは:

test.cpp:(.text+0x130): undefined reference to `test::N'
collect2: ld returned 1 exit status

興味深いことに、std :: minへの呼び出しをコメントアウトすると、コードは正常にコンパイルおよびリンクされます(test :: Nも前の行で参照されていますが)。

何が起こっているかについて何か考えはありますか?

私のコンパイラはLinux上のgcc 4.4です。


3
Visual Studioの2010年に正常に動作します
子犬

4
この正確なエラーは、gcc.gnu.org / wiki /…
Jonathan Wakely

の特定のケースではchar、代わりにとして定義できますconstexpr static const char &N = "n"[0];。に注意してください&。リテラル文字列は自動的に定義されるため、これは機能すると思います。私はこれについてちょっと心配しています-文字列はおそらく複数の異なるアドレスにあるため、異なる変換ユニット間のヘッダーファイルで奇妙な動作をする可能性があります。
アーロン・マクデイド、2015

1
この質問は、「定数に#definesを使用しない」というC ++の回答がいかに不十分であるかを示しています。
Johannes Overmann

1
@JohannesOvermannこの点で、C ++ 17以降のグローバル変数のインラインの使用について言及したいと思いinline const int N = 10ます。私の知る限り、リンカによってどこかにストレージが定義されています。この場合、キーワードインラインを使用して、クラス定義テスト内で静的変数を定義することもできます。
ウォーマー

回答:


72

私の理解では、C ++では、静的なconstメンバーが整数型である限り、クラス内で定義できます。

あなたはちょっと正しいです。クラス宣言で静的定数積分を初期化することは許可されていますが、それは定義ではありません。

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=/com.ibm.xlcpp8a.doc/language/ref/cplr038.htm

興味深いことに、std :: minへの呼び出しをコメントアウトすると、コードは正常にコンパイルおよびリンクされます(test :: Nも前の行で参照されていますが)。

何が起こっているかについて何か考えはありますか?

std :: minはconst参照によってパラメーターを受け取ります。値によってそれらを取得した場合、この問題は発生しませんが、参照が必要なため、定義も必要です。

ここに章/詩があります:

9.4.2 / 4 -場合staticのデータメンバーはであるconst一体型またはconst列挙型、クラス定義の宣言を指定することができ、定初期積分定数式(5.19)でなければなりません。その場合、メンバーは整数定数式で表すことができます。メンバーは、プログラムで使用され、ネームスペーススコープ定義に初期化子が含まれていない場合、ネームスペーススコープで定義されます

可能な回避策については、Chuの回答を参照してください。


なるほど、面白いですね。その場合、宣言の時点で値を提供することと定義の時点で値を提供することの違いは何ですか?どちらがお勧めですか?
HighCommander4 2010年

まあ、私は、変数を実際に「使用」しない限り、定義なしで済むと信じています。定数式の一部としてのみ使用する場合、変数は使用されません。それ以外の場合、ヘッダーの値を確認できることを除いて、大きな違いはないようです。これは、必要な場合とそうでない場合があります。
エドワードストレンジ

2
簡潔な答えは静的const x = 1です。右辺値ですが、左辺値ではありません。値はコンパイル時に定数として使用できます(これを使用して配列をディメンション化できます)static const y; [初期化子なし]はcppファイルで定義する必要があり、右辺値または左辺値として使用できます。
デールウィルソン

2
彼らがこれを拡張/改善できればいいのですが。私の意見では、初期化されたが未定義のオブジェクトはリテラルと同じように扱われるべきです。たとえば、リテラル5をにバインドすることが許可されていconst int&ます。では、なぜOPをtest::N対応するリテラルとして扱わないのでしょうか。
アーロン・マクデイド、2015

興味深い説明、ありがとう!つまり、C ++の静的const intは、整数の#definesの代わりにはなりません。enumは常に署名されたintのみなので、個々の定数にはenumクラスを使用する必要があります。これを問題なくコンパイルする方法で定数を使用して定数宣言を縮退し、値をリテラル定数に知っていることは私には非常に明白です。C ++には長い道のりがあります...
ヨハネスオーバーマン

51

Bjarne Stroustrupの彼のC ++ FAQでの例は、あなたが正しいことを示唆しており、アドレスを取る場合にのみ定義が必要です。

class AE {
    // ...
public:
    static const int c6 = 7;
    static const int c7 = 31;
};

const int AE::c7;   // definition

int f()
{
    const int* p1 = &AE::c6;    // error: c6 not an lvalue
    const int* p2 = &AE::c7;    // ok
    // ...
}

「クラス外の定義がある場合に限り、静的メンバーのアドレスを取得できます」と彼は言います。これは、それが別の方法で機能することを示唆しています。たぶん、あなたのmin関数は何らかの形で舞台裏でアドレスを呼び出します。


2
std::minパラメータを参照で取得するため、定義が必要です。
Rakete1111 2017

AEがテンプレートクラスAE <class T>で、c7がintではなくT :: size_typeである場合、どのように定義を記述しますか?ヘッダーで「-1」に初期化された値を持っていますが、clangは未定義の値を示し、定義の記述方法がわかりません。
Fabian

@Fabian私は旅行中、電話で少し忙しいです...しかし、あなたのコメントは新しい質問として書くのが最も良いように聞こえると思います。アップライトMCVEも多分gccが言うに投げる、あなたが得るエラーを含むが。私は人々が何が何であるかをすぐにあなたに言うだろうに違いない。
HostileForkはSEを信頼してはいけないと言っています

@HostileFork:MCVEを作成するときに、自分で解決策を見つけられる場合があります。私の場合、答えはtemplate<class K, class V, class C> const typename AE<K,V,C>::KeyContainer::size_type AE<K,V,C>::c7;KeyContainerがstd :: vector <K>のtypedefであるところです。依存型であるため、すべてのテンプレートパラメータをリストし、typenameを記述する必要があります。たぶん誰かがこのコメントが役に立つと思うでしょう。ただし、テンプレートクラスはもちろんヘッダーにあるので、これをDLLにエクスポートする方法を考えます。c7をエクスポートする必要がありますか?
Fabian

24

これを行う別の方法は、とにかく整数型の場合、定数をクラスの列挙型として定義することです。

class test
{
public:
    enum { N = 10 };
};

2
そして、これはおそらく問題を解決するでしょう。Nがmin()のパラメーターとして使用されると、おそらく既存の変数を参照するのではなく、一時変数が作成されます。
エドワードストレンジ

これには、プライベートにできるという利点がありました。
Agostino 2018

11

intだけではありません。ただし、クラス宣言で値を定義することはできません。あなたが持っている場合:

class classname
{
    public:
       static int const N;
}

.hファイルには、次のものが必要です。

int const classname::N = 10;

.cppファイル内。


2
クラス宣言内で任意の型の変数を宣言できることを知っています。静的整数定数もクラス宣言内で定義できると思ったと言いました。これはそうではありませんか?そうでない場合、クラス内で定義しようとする行でコンパイラがエラーを出さないのはなぜですか?さらに、なぜstd :: cout行でリンカーエラーが発生しないのに、std :: min行では発生するのですか?
HighCommander4 2010年

いいえ、初期化でコードが生成されるため、クラス宣言で静的メンバーを定義できません。コードも出力するインライン関数とは異なり、静的定義はグローバルに一意です。
Amardeep AC9MF 2010年

@ HighCommander4:static constクラス定義の整数メンバーの初期化子を指定できます。しかし、それでもそのメンバーは定義されいません。詳細については、ノアロバーツの回答を参照してください。
AnT

9

問題を回避する別の方法を次に示します。

std::min(9, int(test::N));

(Crazy Eddieの答えは、問題が存在する理由を正しく説明していると思います。)


5
またはstd::min(9, +test::N);
Tomilov Anatoliy 2013

しかし、ここで大きな問題があります。これはすべて最適ですか?私はあなたたちについては知りませんが、定義をスキップする私の大きな魅力は、const staticを使用するときにメモリやオーバーヘッドをまったく消費しないことです。
Opux

6

C ++ 11以降では、次のものを使用できます。

static constexpr int N = 10;

理論的にはこれでも.cppファイルで定数を定義する必要がありますが、そのアドレスを使用しない限り、Nコンパイラーの実装でエラーが発生する可能性はほとんどありません;)


例のように、値を「const int&」型の引数として渡す必要がある場合はどうでしょうか。:-)
Wormer

それはうまくいきます。そのようにNをインスタンス化するのではなく、単にconst参照を一時変数に渡します。wandbox.org/permlink/JWeyXwrVRvsn9cBj
カルロウッド

C ++ 17はおそらくC ++ 14ではなく、以前のバージョンのgcc 6.3.0以下ではC ++ 17でもなく、標準ではありません。しかし、これに言及してくれてありがとう。
ウォーマー

ああ、そうです。wandboxでc ++ 14を試していません。ああ、それは私が「これは理論的にはまだ定数を定義する必要がある」と言った部分です。だから、あなたはそれが「標準」ではないことは正しいです。
カルロウッド

3

C ++では、静的なconstメンバーをクラス内で定義できます

いいえ、3.1§2はこう言っています:

宣言は定義であり、関数の本体(8.4)を指定せずに関数を宣言し、extern指定子(7.1.1)またはリンケージ仕様(7.5)を含み、初期化子も関数本体も含まない限り、静的データを宣言しますクラス定義(9.4)のメンバー、クラス名宣言(9.1)、不透明な列挙型宣言(7.2)、またはtypedef宣言(7.1.3)、使用宣言(7.3。 3)、またはusingディレクティブ(7.3.4)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.