41

Eckel、Vol 1、pg:367

//: C08:ConstReturnValues.cpp
// Constant return by value
// Result cannot be used as an lvalue
class X {
   int i;
public:
   X(int ii = 0);
   void modify();
};

X::X(int ii) { i = ii; }

void X::modify() { i++; }

X f5() {
   return X();
}

const X f6() {
   return X();
}

void f7(X& x) { // Pass by non-const reference
   x.modify();
}

int main() {
   f5() = X(1); // OK -- non-const return value
   f5().modify(); // OK
// Causes compile-time errors:
//! f7(f5());
//! f6() = X(1);
//! f6().modify();
//! f7(f6());
} ///:~

なぜf5() = X(1)成功したのですか?ここで何が起こっているのですか?

Q1。彼がそうするときX(1)-ここで何が起こっているのですか?これはコンストラクター呼び出しですか?これを読んではいけませんX::X(1);か?それはクラスのインスタンス化ですか-クラスのインスタンス化は次のようなものではありません:X a(1);コンパイラーはどのように何 X(1)であるかを判別しますか?つまり、名前の装飾が行われるのでX(1)、コンストラクター呼び出しは次のように変換されます:globalScope_X_int関数名として.. ???

Q2。確かに、一時オブジェクトは、X(1) 作成された結果のオブジェクトを格納するために使用され、その後、オブジェクトf5()リターンに割り当てられませんか(これも一時オブジェクトになります)?それがすぐに破棄される一時オブジェクトをf5()返すとすると、彼はどのようにしてある定数一時変数を別の定数一時変数に割り当てることができますか?誰かが理由 f7(f5());を明確に説明できますか?f5();

4

4 に答える 4

38

すべての質問は、一時オブジェクト(名前のないオブジェクト)を非定数参照にバインドできないというC++のルールに要約されます。(Stroustrupは論理エラーを引き起こす可能性があると感じたため...)

一つの落とし穴は、一時的にメソッドを呼び出すことができるということです。それでX(1).modify()問題ありませんが、そうでf7(X(1))はありません。

一時的なものが作成される場所に関しては、これはコンパイラーの仕事です。言語の規則は、一時的なものが現在の完全な式の終わりまでのみ存続する必要があることを正確に示しています。これは、デストラクタに副作用があるクラスの一時的なインスタンスにとって重要です。

したがって、次のステートメントX(1).modify();は完全に次のように翻訳できます。

{
    X __0(1);
    __0.modify();
} // automatic cleanup of __0

それを念頭に置いて、攻撃することができf5() = X(1);ます。ここには2つの一時的なものと1つの割り当てがあります。割り当てが呼び出される前に、割り当ての両方の引数を完全に評価する必要がありますが、順序は正確ではありません。考えられる翻訳の1つは次のとおりです。

{
    X __0(f5());
    X __1(1);
    __0.operator=(__1);
}

(もう1つの変換は、が初期化される順序を入れ替えることです__0__1

そして、それが機能するための鍵__0.operator=(__1)は、メソッドの呼び出しであり、メソッドは一時的に呼び出すことができるということです:)

于 2012-06-05T13:32:20.323 に答える
19

私は答えに完全に満足していなかったので、私は見てみました:

「より効果的なC++」、スコットマイヤーズ。項目19:「一時的なオブジェクトの起源を理解する」

。ブルース・エッケルの「Temporaries」の報道に関しては、私が思うに、そしてクリスチャン・ラウが直接指摘しているように、それは明らかに間違っています!Grrr!彼は(エッケルの)私たちをモルモットとして使っています!! (彼がすべての間違いを訂正したら、それは私のような初心者にとって良い本になるでしょう)

Meyer:「C ++の真の一時オブジェクトは表示されません。ソースコードには表示されません。ヒープ以外のオブジェクトが作成されても名前が付けられていない場合に発生します。このような名前のないオブジェクトは通常、次の2つの状況のいずれかで発生します。暗黙的な型変換の場合。関数呼び出しを成功させるため、および関数がオブジェクトを返すときに適用されます。」

「最初に、関数呼び出しを成功させるために一時オブジェクトが作成される場合を考えてみてください。これは、関数に渡されるオブジェクトのタイプが、バインドされているパラメーターのタイプと同じでない場合に発生します。」

「これらの変換は、オブジェクトを値で渡す場合、またはreference-to-constパラメーターに渡す場合にのみ発生します。オブジェクトをreference-to-non-constパラメーターに渡す場合には発生しません。」

「一時オブジェクトが作成される2番目の状況は、関数がオブジェクトを返すときです。」

「constへの参照パラメーターが表示されると、そのパラメーターにバインドするための一時オブジェクトが作成される可能性があります。オブジェクトを返す関数が表示されると、一時オブジェクトが作成されます(後で破棄されます)。」

答えの他の部分は、「はじめに」の「マイヤー:効果的なC++」にあります。

「コピーコンストラクタは、同じタイプの別のオブジェクトでオブジェクトを初期化するために使用されます。」

String s1;       // call default constructor
String s2(s1);   // call copy constructor
String s3 = s2;  // call copy constructor

「おそらく、コピーコンストラクターの最も重要な使用法は、オブジェクトを値で渡したり返したりすることの意味を定義することです。」

私の質問について:

f5() = X(1) //what is happening?

ここでは、新しいオブジェクトは初期化されていません。これは初期化ではありません(コピーコンストラクター)。これは割り当てです(Matthieu Mが指摘したように)。

Meyer(上の段落)に従って、両方の関数が値を返すため、一時オブジェクトが作成され、一時オブジェクトが作成されます。Matthieuが擬似コードを使用して指摘したように、次のようになります。 __0.operator=(__1)ビット単位のコピーが実行されます(コンパイラによって実行されます)。

それにかんする:

void f7(X& x);
f7(f5);

エルゴ、一時的なものは作成できません(マイヤー:上の段落)。宣言されていた場合:void f7(const X& x);一時的なものが作成されます。

一時オブジェクトが定数であることについて:

Meyerはそれ(およびMatthieu)を次のように述べています。「そのパラメーターにバインドするための一時的なものが作成されます。」

したがって、一時オブジェクトは定数参照にのみバインドされ、それ自体は「const」オブジェクトではありません。

に関して:何X(1)ですか?

Meyer、Item27、Effective C ++-3e、彼は次のように述べています。

"Cスタイルのキャストは次のようになります:(T)expression//式をT型にキャストします

関数スタイルのキャストは、次の構文を使用します。T(expression)//式をT型にキャストします。 "

X(1)関数スタイルのキャストもそうです。1式はタイプにキャストされていXます。

そしてマイヤーは再びそれを言います:

「古いスタイルのキャストを使用するのは、明示的なコンストラクターを呼び出してオブジェクトを関数に渡す場合だけです。例:

class Widget {
  public:
    explicit Widget(int size);
    ...
};

void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); //create Widget from int
                        //with function-style cast

doSomeWork(static_cast<Widget>(15));

どういうわけか、意図的なオブジェクトの作成はキャストのように「感じ」ないので、この場合はstatic_castの代わりに関数スタイルのキャストを使用するでしょう。」

于 2012-06-10T05:08:06.780 に答える
7
  1. これは確かにコンストラクター呼び出しであり、型の一時オブジェクトを評価する式Xです。X([...])型の名前であるフォームの式はX、型の一時オブジェクトを作成するコンストラクター呼び出しですX(ただし、適切な標準でそれを説明する方法はわかりません。また、パーサーの動作が異なる特殊なケースがあります)。これは、f5および関数で使用するのと同じ構成ですが、オプションの引数f6を省略しています。ii

  2. ライフによって作成された一時的なものX(1)(破棄/無効化されない)は、それを含む完全な式が終了するまで(この場合は代入式の場合のように)セミコロンまでを意味します。同様にf5、一時的なものを作成し、Xそれを呼び出しサイト(内部main)に返し、コピーします。したがって、メインでは、f5呼び出しは一時的なも返しますX。この一時的なものには、によって作成されXた一時的なものが割り当てられます。それが行われた後(そして必要に応じてセミコロンに到達した後)、両方の一時的なものが破棄されます。これらの関数は通常の非定数を返すため、この割り当ては機能しますXX(1)オブジェクトは、式が完全に評価された後に一時的で破棄されたものであるかどうかに関係なく(したがって、完全に有効であっても、割り当ては多かれ少なかれ無意味になります)。

    それはあなたが割り当てることができないにf6戻るので、それは動作しません。一時オブジェクトと一時オブジェクトを作成すると、非定数左辺値参照にバインドされないため、const X同様に機能しませf7(f5())ん(C ++ 11ではこの目的で右辺値参照が導入されましたが、これは別の話です)。定数左辺値参照は一時的なものにバインドされるため、const参照を取得すると機能します(ただし、もちろん、それ自体は機能しなくなります)。f5X&X&&f7const X&f7

于 2012-06-05T13:31:42.537 に答える
1

これは、コードを実行したときに実際に何が起こるかの例です。舞台裏のプロセスを明確にするために、いくつかの変更を加えました。

#include <iostream>

struct Object
{
    Object( int x = 0 ) {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
    ~Object() {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
    Object( const Object& rhs ){std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;}
    Object& operator=( const Object& rhs )
    {
        std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;
        return *this;
    }
    static Object getObject()
    {
        return Object();
    }
};

void TestTemporary()
{
    // Output on my machine
    //0x22fe0e: Object::Object(int) -> The Object from the right side of = is created Object();
    //0x22fdbf: Object::Object(int) -> In getObject method the Temporary Unnamed object is created
    //0x22fe0f: Object::Object(const Object&) rhs = 0x22fdbf -> Temporary is copy-constructed from the previous line object
    //0x22fdbf: Object::~Object() -> Temporary Unnamed is no longer needed and it is destroyed
    //0x22fe0f: Object& Object::operator=(const Object&) rhs = 0x22fe0e -> assignment operator of the returned object from getObject is called to assigne the right object
    //0x22fe0f: Object::~Object() - The return object from getObject is destroyed
    //0x22fe0e: Object::~Object() -> The Object from the right side of = is destroyed Object();

    Object::getObject() = Object();
}

最近のほとんどのコンパイラでは、コピーの構築が回避されることを知っておく必要があります。これは、コンパイラーによる最適化(戻り値の最適化)によるものです。私の出力では、標準に従って実際に何が起こるかを示すために、最適化を明示的に削除しました。この最適化も削除したい場合は、次のオプションを使用してください。

-fno-elide-constructors
于 2016-10-11T13:55:58.860 に答える