6

したがって、C++ では、関数の戻り値を const 参照に割り当てると、その戻り値の有効期間がその参照のスコープになります。例えば

MyClass GetMyClass()
{
    return MyClass("some constructor");
}

void OtherFunction()
{
    const MyClass& myClass = GetMyClass(); // lifetime of return value is until the end            
                                           // of scope due to magic const reference
    doStuff(myClass);
    doMoreStuff(myClass);
}//myClass is destructed

そのため、通常は関数からの戻り値を const オブジェクトに割り当てる場合は、代わりに const 参照に割り当てることができるようです。代入で参照を使用せず、代わりにオブジェクトを使用したい関数のケースはありますか? なぜあなたは行を書きたいのですか:

const MyClass myClass = GetMyClass();

編集:私の質問は数人の人々を混乱させたので、GetMyClass関数の定義を追加しました

編集 2: これを読んでいない場合は、質問に答えようとしないでください: http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-定数/

4

6 に答える 6

4

関数が (参照ではなく) オブジェクトを返す場合は、呼び出し元の関数でコピーを作成する必要があります [ただし、最適化手順が実行される場合があります。これは、コピーが終了する結果のストレージにオブジェクトが直接書き込まれることを意味します。 「あたかも」の原則]。

サンプル コードでconst MyClass myClass = GetMyClass();は、この「コピー」オブジェクトはmyclass、存在するが名前が付けられていない (またはマシン コードを見ないと表示されない) 一時オブジェクトではなく、名前が付けられています。つまり、変数を宣言するかどうかに関係なく、MyClass関数呼び出し内にオブジェクトが存在しGetMyClassます。これは、それを可視にするかどうかの問題です。

Edit2:const参照ソリューションは同様に表示されます(同一ではなく、これは私が何を意味するかを説明するために実際に書かれたものであり、実際にはこれを行うことはできません):

 MyClass __noname__ = GetMyClass();
 const MyClass &myclass = __noname__;

コンパイラが__noname__実際にそれについて知らせることなく、舞台裏で変数を生成するだけです。

を作成するとconst MyClass myclass、オブジェクトが表示され、何が起こっているかが明確になります (また、GetMyClassが既存のオブジェクトへの参照ではなく、オブジェクトの COPY を返していることがわかります)。

一方、GetMyClassが実際に参照を返す場合、それは確かに正しいことです。

一部のコンパイラでは、参照を使用すると、オブジェクトが使用されているときに余分なメモリ読み取りが追加されることさえあります.味を良くするために追加の砂糖を使用して参照をポインターとして実装しないコンパイラーを教えてください] したがって、参照を使用するには、コンパイラーは参照値 (オブジェクトへのポインター) を読み取り、次に値を読み取る必要があります。そのポインターからオブジェクト内。非参照の場合、オブジェクト自体は参照ではなく直接オブジェクトとしてコンパイラに「認識」され、余分な読み取りが保存されます。確かに、ほとんどのコンパイラはほとんどの場合、そのような余分な参照を最適化しますが、常にそうできるとは限りません。

于 2013-08-09T10:06:44.220 に答える
0

実際に呼び出されるデストラクタに関しては、大きな意味があります。Gotw88、Q3、A3 を確認してください。私はすべてを小さなテストプログラムに入れました(Visual-C++なので、stdafx.hを許してください)

// Gotw88.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>

class A
{
protected:
    bool m_destroyed;
public:
    A() : m_destroyed(false) {}
    ~A() 
    { 
        if (!m_destroyed)
        {
            std::cout<<"A destroyed"<<std::endl;
            m_destroyed=true;
        }
    }
};

class B : public A
{
public:
    ~B() 
    { 
        if (!m_destroyed)
        {
            std::cout<<"B destroyed"<<std::endl;
            m_destroyed=true;
        }
    }
};

B CreateB()
{
    return B();
}


int _tmain(int argc, _TCHAR* argv[])
{
    std::cout<<"Reference"<<std::endl;
    {
        const A& tmpRef = CreateB();
    }
    std::cout<<"Value"<<std::endl;
    {
        A tmpVal = CreateB();
    }


    return 0;
}

この小さなプログラムの出力は次のとおりです。

Reference
B destroyed
Value
B destroyed
A destroyed

ここで、セットアップの簡単な説明を行います。B は A から派生していますが、どちらにも仮想デストラクタがありません (これが WTF であることはわかっていますが、ここで重要です)。CreateB() は B を値で返します。Main は CreateB を呼び出し、最初にこの呼び出しの結果を型 A の const 参照に格納します。次に、CreateB が呼び出され、結果が型 A の値に格納されます。

結果は興味深いものです。まず、参照によって格納する場合は正しいデストラクタが呼び出され (B)、値によって格納する場合は間違ったデストラクタが呼び出されます。2 つ目 - 参照に格納する場合、デストラクタは 1 回だけ呼び出されます。これは、オブジェクトが 1 つしかないことを意味します。値によって、(異なるデストラクタへの) 2 つの呼び出しが発生します。つまり、2 つのオブジェクトがあることを意味します。

私のアドバイス - const 参照を使用してください。少なくとも Visual C++ では、コピーが少なくなります。使用しているコンパイラが不明な場合は、このテスト プログラムを使用して適合させ、コンパイラをチェックしてください。適応する方法は?コピー/移動コンストラクターとコピー代入演算子を追加します。


クラスAとBのコピーと代入演算子をすぐに追加しました

A(const A& rhs)
{
    std::cout<<"A copy constructed"<<std::endl;
}

A& operator=(const A& rhs)
{
    std::cout<<"A copy assigned"<<std::endl;
}

(B についても同じです。大文字の A をすべて B に置き換えてください)

これにより、次の出力が得られます。

Reference
A constructed
B constructed
B destroyed
Value
A constructed
B constructed
A copy constructed
B destroyed
A destroyed

これにより、上記の結果が確認されます (B が A から派生しているため、構築された B から構築された A の結果に注意してください。したがって、Bs コンストラクターが呼び出されるたびに As コンストラクターが呼び出されます)。

追加のテスト: Visual C++ は、const 参照と同じ結果 (この例) を持つ非 const 参照も受け入れます。さらに、タイプとして auto を使用すると、(もちろん) 正しいデストラクタが呼び出され、戻り値の最適化が開始され、最終的には const 参照と同じ結果になります (ただし、もちろん、auto には A ではなくタイプ B があります)。 .

于 2013-08-09T12:06:30.237 に答える
0

パフォーマンス

現在のほとんどのコンパイラはコピー (および移動) を省略しているため、両方のバージョンでほぼ同じ効率が得られるはずです。

const MyClass& rMyClass = GetMyClass();
const MyClass  oMyClass = GetMyClass();

2 番目のケースでは、コピーまたは移動のいずれかが意味的に必要ですが、[class.copy]/31 に従って省略できます。わずかな違いは、最初のものはコピー不可、移動不可のタイプで機能することです。

Mats Petersson と James Kanze によって、一部のコンパイラでは参照へのアクセスが遅くなる可能性があることが指摘されています。


一生

参照は、自動ストレージを持つオブジェクトと同様に、スコープ全体で有効でなければなりません。もちろん、この「すべき」は、プログラマーによって強制されることを意図しています。したがって、読者の IMO にとって、それらによって暗示される寿命に違いはありません。ただし、バグがあった場合は、おそらくダングリング リファレンスを探します (元のコードやリファレンスのライフタイム クレームを信頼していません)。

GetMyClass参照を返すように (合理的に) 変更される可能性がある場合は、そのオブジェクトの有効期間が十分であることを確認する必要があります。

SomeClass* p = /* ... */;

void some_function(const MyClass& a)
{
    /* much code with many side-effects */
    delete p;
    a.do_something();  // oops!
}

const MyClass& r = p->get_reference();
some_function(r);

所有

I own this object のようにオブジェクトを直接指定する変数は、 I own this object をconst MyClass oMyClass;明確に示します。メンバーを考慮してください。後で変更する場合、参照として宣言されている場合、(すべての変更に対して) OK であるかどうかが読者にはすぐにはわかりません。mutable

さらに、参照の場合、参照先のオブジェクトが変更されないことは明らかではありません参照は、誰もオブジェクトを変更しないということではなく、オブジェクトを変更しないconstことを意味するだけです( *)。プログラマーは、変数の定義を調べて、この参照がそのオブジェクトを参照する唯一の方法であることを知っておく必要があります。

(*) 免責事項: 目に見えない副作用を避けるようにしてください

于 2013-08-09T11:22:43.653 に答える
0

あなたが何を達成したいのか理解できません。T const&関数から返される (値によって) に (スタック上で) バインドできる理由Tは、他の関数がこの一時的なT const&引数を受け取ることができるようにするためです。これにより、オーバーロードを作成する必要がなくなります。ただし、戻り値はとにかく構築する必要があります。

しかし、今日 (C++11 で) を使用できますconst auto myClass = GetMyClass();

編集: 何が起こるかの例として、私は何かを提示します:

MyClass version_a();
MyClass const& version_b();

const MyClass var1 =version_a();
const MyClass var2 =version_b();

const MyClass var3&=version_a();
const MyClass var4&=version_b();

const auto    var5 =version_a();
const auto    var6 =version_b();
  • var1の結果で初期化されますversion_a()
  • var2version_b()によって返される参照が属するオブジェクトのコピーで初期化されます
  • var3返されたテンポラリーへの const 参照を保持し、その寿命を延長します
  • var4から返された参照で初期化されますversion_b()
  • var5と同じvar1
  • var6と同じvar4

それらは意味論的にすべて異なります。var3上記の理由で機能します。返されたものだけvar5var6自動的に保存します。

于 2013-08-09T10:36:42.790 に答える