3

C++参照はまだ私を混乱させています。Foo型のオブジェクトを作成し、それを参照によって返す関数/メソッドがあるとします。(オブジェクトを返したい場合は、スタックに割り当てられたローカル変数にすることはできないと思います。そのため、オブジェクトをヒープに割り当てる必要がありますnew):

Foo& makeFoo() {
    ...
    Foo* f = new Foo;
    ...
    return *f;
}

別の関数のローカル変数に作成されたオブジェクトを格納したい場合、タイプは次のようになります。Foo

void useFoo() {

    Foo f = makeFoo();
    f.doSomething();
}

またはFoo&

void useFoo() {

    Foo& f = makeFoo();
    f.doSomething();
}

どちらも正しい構文なので、2つのバリアントの間に大きな違いはありますか?

4

3 に答える 3

5

はい、最初のものは返された参照のコピーを作成し、2番目のものはの戻り値への参照になりますmakeFoo

コピーコンストラクター内でダークマジックを実行しない限り、最初のバージョンを使用するとメモリリークが発生する可能性が高いことに注意してください。

さて、あなたが呼ばない限り、2番目もリークになりますdelete &f;

結論:しないでください。群衆を追いかけ、価値によって戻ってください。またはスマートポインタ。

于 2013-01-07T14:57:00.807 に答える
3

最初のコードは多くの作業を行います。

void useFoo() {
    Foo f = makeFoo();  // line 2
    f.doSomething();
}

2行目を考えると、いくつかの興味深いことが起こります。まず、コンパイラは、クラスのデフォルトコンストラクタを使用してFooオブジェクトを構築するためのコードを発行します。f次に、を呼び出しますmakeFoo()。これにより、新しいFooオブジェクトが作成され、そのオブジェクトへの参照が返されます。makeFoo()コンパイラはまた、の一時的な戻り値をオブジェクトにコピーするコードを発行する必要がありfます。そうすると、一時的なオブジェクトが破棄されます。2行目が完了すると、f.doSomething()が呼び出されます。ただし、オブジェクトがスコープ外になっているため、戻る直前にuseFoo()、でオブジェクトも破棄します。f

2番目のコード例ははるかに効率的ですが、実際にはおそらく間違っています。

void useFoo() {
    Foo& f = makeFoo();   // line 2
    f.doSomething();
}

fその例の2行目を考えると、これは単なる参照であるため、オブジェクトを作成しないことがわかります。このmakeFoo()関数は、新しく割り当てられたオブジェクトを返し、そのオブジェクトへの参照を保持します。私たちはdoSomething()その参照を通して呼びます。しかし、useFoo()関数が戻ったときに、作成した新しいオブジェクトを破棄することはなく、makeFoo()リークします。

これを修正する方法はいくつかあります。追加のコンストラクター、作成、コピー、および破棄を気にしない場合は、最初のコードフラグメントにある参照メカニズムを使用できます。(些細なコンストラクタとデストラクタがあり、コピーする状態があまりない(またはまったくない)場合は、それほど重要ではありません。)ポインタを返すだけで済みます。これは、呼び出し元がライフの管理に責任があることを強く示唆しています。参照されるオブジェクトのサイクル。

ポインタを返す場合は、呼び出し元がオブジェクトのライフサイクルを管理する必要があることを意味していますが、それを強制していません。誰か、いつか、どこかでそれを間違えるでしょう。したがって、参照を管理し、オブジェクトの管理をカプセル化するためのアクセサーを提供するラッパークラスを作成することを検討してください。(必要に応じて、それをクラス自体に組み込むこともできFooます。)このタイプのラッパークラスは、一般的な形式では「スマートポインター」と呼ばれます。STLを使用している場合はstd::unique_ptrテンプレートクラスにスマートポインタの実装があります。

于 2013-01-07T15:03:47.623 に答える
2

関数は、作成される新しいオブジェクトへの参照を決して返さないようにする必要があります。新しい値を作成するときは、値またはポインタを返す必要があります。ほとんどすべてのコンパイラがRVO/NRVOを使用して余分なコピーを取り除くため、値を返すことをお勧めします。

値を返す:

Foo makeFoo(){
    Foo f;
    // do something
    return f;
}

// Using it
Foo f = makeFoo();

ポインタを返す:

Foo* makeFoo(){
    std::unique_ptr<Foo> p(new Foo());  // use a smart pointer for exception-safety
    // do something
    return p.release();
}

// Using it
Foo* foo1 = makeFoo();                 // Can do this
std::unique_ptr<Foo> foo2(makeFoo());   // This is better
于 2013-01-07T15:27:24.550 に答える