4

実際のプロジェクトで見られる問題に基づいて考案した次の簡単なクラスについて考えてみます。constexprTripleは、クラスFooのインナーで使用するクイックボイラープレートタイプです。

#include <iostream>

class Triple {
public:
    friend
    std::ostream & operator <<(std::ostream & o, Triple const & t);

    constexpr Triple() : a_(0), b_(0), c_(0) { }
    constexpr Triple(Triple const & other) = default;
    constexpr Triple(double a, double b, double c)
      : a_(a), b_(b), c_(c)
    { }

    ~Triple() = default;

private:
    double a_, b_, c_;
};

std::ostream & operator <<(std::ostream & o, Triple const & t) {
    o << "(" << t.a_ << ", " << t.b_ << ", " << t.c_ << ")";
    return o;
}

class Foo {
public:
    Foo() : triple_(defaultTriple) { }

    Triple const & triple() const { return triple_; }
    Triple & triple() { return triple_; }

    constexpr static float defaultPOD{10};
    constexpr static Triple defaultTriple{11.0, 22.0, 33.0};

private:
    Triple triple_;
};

次にmain()、のパブリック内部を使用する関数をconstexpr次のように作成Fooすると、リンクに失敗します(Windows7ではmingw-x86-64を介してg++ 4.7.0を使用)。

int main(int argc, char ** argv) {
    using std::cout;
    using std::endl;

    cout << Foo::defaultPOD << endl;
    cout << Foo::defaultTriple << endl;
}
    $ g ++ -o test -O3 --std = c ++ 11 test.cpp
    e:\ temp \ ccwJqI4p.o:test.cpp:(。text.startup + 0x28): `Foo :: defaultTriple'への未定義の参照collect2.exe:エラー:ldが1つの終了ステータスを返しました

しかし、私が書くと

cout << Triple{Foo::defaultTriple} << endl

単にではなく

cout << Foo::defaultTriple << endl

リンクして正常に実行されます。前者はコンパイル時リテラルが意図されたものであることをより明確に表現していることがわかりますが、後者がうまく機能しないことにまだ驚いています。constexprこれはコンパイラのバグですか、それとも最初の例だけが機能するというルールに基づく理由がありますか?

より多くの洞察を得るために他のコンパイラを試してみますが、現在、GCC4.7.0は私がアクセスできる唯一のコンパイラですconstexpr

constexprポッドの式は、明示的なリテラルラッパーがなくても正常に機能することにも注意してください。たとえば、cout << Foo::defaultPOD問題が発生したことはありません。

4

3 に答える 3

3

定数式が不要なコンテキストで表示される定数は、プログラムの変換中に評価される場合がありますが、必須ではないため、実行時に評価される場合があります。

プログラム変換中にメンバーが評価される場合constexpr static、コンパイラーはその初期化子を使用してその値を判別でき、メンバーの定義は必要ありません。

メンバーが実行時に評価されるコンテキストで使用される場合は、その定義が必要になります。

cout << Foo::defaultTriple << endlコンパイラでは、実行時に左辺値から右辺値への変換を実行するコードを生成しているFoo::defaultTripleため、オブジェクトには定義が必要です。

cout << Triple{Foo::defaultTriple} << endlコンパイラでは、Foo::defaultTripleプログラムの変換中にTriple評価を行って、実行時に評価される可能性のある一時的なものを作成しています。

オブジェクトが定数式が必要なconstexprコンテキストでのみ評価される場合を除き、オブジェクトの定義を提供する必要があります。

于 2012-06-12T06:58:40.873 に答える
2

defaultPODクラス内でdefaultTriple宣言されているのは定義ではありません。それらのアドレスを知る必要がある場所でそれらを使用したい場合は、クラス宣言の外でそれらを定義する必要があります。

では、なぜ機能cout << Foo::defaultPOD << endl;するのに機能cout << Foo::defaultTriple << endl;しないのでしょうか。

defaultPODはとして宣言されているfloatので、これを行うと、引数を値cout << Foo::defaultPODで受け取るを呼び出します。のみを使用しているため、この呼び出しで定義は必要ありません(3.2.3で定義されているようにodr-usedではありません)。参照を受け取る関数に渡そうとする場合は、それを定義する必要があります。operator<<(float val);Foo::defaultPOD

ただし、定義が必要な参照による取得が必要なため、Foo::defaultTriple失敗します。ただし、値渡しに変更した後でも、私のテストでは、リンカーエラーが発生しました。メンバー変数をから削除して値を渡す場合にのみ、静的メンバー変数を定義せずにコードがコンパイルされます。(コンパイラーからメンバー変数を削除すると、私が信じる変数が最適化されます)。operator <<TripleFoo::defaultTripleoperator<<Tripleoperator<<Triple

(これは、このようなもののいくつかを説明する素晴らしいリファレンスです)。

于 2012-06-12T01:33:56.677 に答える
1

エラーはリンカーから発生し、Foo::defaultTriple静的メンバーを見つけることができません。

ここでの問題は、「宣言」と「定義」の違いです。クラスの静的な行は宣言です。定義も必要です。C ++では、statica内で定義されたすべてのフィールドはclass、.cppファイル内にも存在する必要があります。

// .hpp

class X {
    static int Q;
};

// .cpp

int X:Q = 0;

あなたの場合、この行は.cppファイルのどこかにあるはずです。

Triple foo::defaultTriple;
于 2012-06-12T01:22:32.097 に答える