web-dev-qa-db-ja.com

一時オブジェクト-作成されたとき、コードでどのように認識しますか?

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()がすぐに破棄される一時オブジェクトを返す場合、どのようにして1つの定数テンポラリーを別の定数テンポラリーに割り当てることができますか?誰かが理由を明確に説明してもらえますか:f7(f5());は単純な古いf5();ではなく、一定の一時的な結果になります

33
user621819

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

「より効果的なC++」、Scott Meyers。項目19:「一時オブジェクトの起源を理解する」

。 Bruce Eckelの「Temporaries」の報道に関しては、私が疑うように、そしてChristian Rauが直接指摘しているように、それは明らかに間違っています! Grrr!彼は私たちをモルモットとして使っています(Eckel's)!! (私のような初心者がすべての間違いを訂正したら、それは良い本になるでしょう)

Meyer:「C++の真の一時オブジェクトは表示されません。ソースコードには表示されません。非ヒープオブジェクトが作成され、名前が付けられない場合に発生します。関数呼び出しを成功させるため、および関数がオブジェクトを返すときに適用されます。」

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

「これらの変換は、オブジェクトを値で渡すとき、または参照から定数へのパラメーターを渡すときにのみ発生します。参照から非定数へのオブジェクトを渡すときには発生しません。」

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

「constへの参照パラメーターが表示されるときはいつでも、そのパラメーターにバインドするための一時ファイルが作成される可能性があります。オブジェクトを返す関数が表示されるたびに、一時ファイルが作成されます。

答えの他の部分は、「はじめに」の「Meyer:Effective 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);
_

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

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

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

したがって、temporaryは定数参照にのみバインドされ、それ自体は「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の代わりに関数スタイルのキャストを使用するでしょう。

15
user621819

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

1つの問題は、一時的にメソッドを呼び出すことができることです。したがって、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);
}
_

他の変換は___0_と___1_が初期化される順序を交換しています

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

33
Matthieu M.
  1. これは確かにコンストラクター呼び出し、タイプXの一時オブジェクトに評価される式です。型の名前がXであるX([...])という形式の式は、X型の一時オブジェクトを作成するコンストラクター呼び出しです(説明する方法はわかりませんが)適切な標準では、パーサーが異なる動作をする特別なケースがあります)。これは_f5_および_f6_関数で使用するものと同じ構成であり、オプションのii引数を省略します。

  2. X(1)によって作成されたテンポラリは、それを含む完全な式の終わりまで存続します(破壊/無効になりません)。これは通常、(この場合の割り当て式のように)セミコロンまでを意味します。同様に、_f5_は一時的なXを作成し、それを呼び出しサイト(main内)に返し、コピーします。したがって、主に_f5_呼び出しは一時的なXも返します。この一時的なXには、X(1)によって作成された一時的なXが割り当てられます。それが完了すると(必要に応じてセミコロンに到達します)、両方の一時ファイルが破棄されます。これらの関数は、式が完全に評価された後に一時的で破棄された場合でも、これらの関数が通常のnon-constantオブジェクトを返すため、機能します(したがって、完全に有効であっても、割り当ては多少無意味になります) 。

    _f6_は、割り当てられない_const X_を返すため、動作しません。同様に、f7(f5())は機能しません。_f5_は一時オブジェクトを作成し、一時オブジェクトは非const左辺値参照にバインドしません_X&_(C++ 11で導入された右辺値参照_X&&_この目的のためですが、それは別の話です)。定数左辺値参照が一時にバインドするため、_f7_がconst参照_const X&_を取得した場合に機能します(ただし、_f7_自体はもはや機能しません)。

6
Christian Rau

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

#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
1
stanimirco