12

私は参照を理解する時間の悪魔を持っています。次のコードを検討してください。

class Animal
{
public:
    virtual void makeSound() {cout << "rawr" << endl;}
};

class Dog : public Animal
{
public:
    virtual void makeSound() {cout << "bark" << endl;}
};

Animal* pFunc()
{
    return new Dog();
}

Animal& rFunc()
{
    return *(new Dog());
}

Animal vFunc()
{
    return Dog();
}

int main()
{
    Animal* p = pFunc();
    p->makeSound();

    Animal& r1 = rFunc();
    r1.makeSound();

    Animal r2 = rFunc();
    r2.makeSound();

    Animal v = vFunc();
    v.makeSound();
}

そして結果は次のとおりです:「樹皮樹皮rawrrawr」。

Javaの考え方では(これは明らかに私のC ++の概念を破壊しました)、結果は「樹皮樹皮樹皮樹皮」になります。以前の質問から、この違いはスライスによるものであることがわかり、スライスとは何かをよく理解できました。

しかし、実際には犬である動物の値を返す関数が必要だとしましょう。

  1. 私が得ることができる最も近いものが参照であることを正しく理解していますか?
  2. さらに、rFuncインターフェースを使用して、返される参照がAnimal&に割り当てられていることを確認する必要がありますか?(または、スライスを介して多型を破棄する動物への参照を意図的に割り当てます。)
  3. 上記のrFuncで行った愚かなことを行わずに、いったいどうやって新しく生成されたオブジェクトへの参照を返すことになっているのでしょうか。(少なくとも、これはばかげていると聞きました。)

更新:これまでのところ、rFuncは違法であることに誰もが同意しているように見えるため、別の関連する質問が発生します。

ポインターを返す場合、そのポインターが削除するものではないことをプログラマーにどのように伝えるのですか?または、ポインターがいつでも(同じスレッドから異なる関数から)削除されることを通知して、呼び出し元の関数がポインターを格納しないようにするにはどうすればよいですか。コメントを通じてこれを伝える唯一の方法はありますか?それはずさんなようです。

注:これはすべて、私が取り組んでいたテンプレート化されたshared_pimplの概念のアイデアにつながります。うまくいけば、私は数日でそれについて何かを投稿するのに十分なことを学ぶでしょう。

4

8 に答える 8

6

1)新しいオブジェクトを作成している場合は、参照を返したくありません(#3に関する独自のコメントを参照してください)。ポインターを返すことができます(std::shared_ptrまたはでラップされている可能性がありますstd::auto_ptr)。(コピーで返すこともできますが、これはnew演算子の使用と互換性がありません。ポリモーフィズムともわずかに互換性がありません。)

2)rFunc間違っています。そうしないでください。オブジェクトを作成していた場合newは、(オプションでラップされた)ポインターを介してオブジェクトを返します。

3)あなたはそうするべきではありません。それがポインタの目的です。


編集(更新に対応:)説明しているシナリオを想像するのは難しいです。呼び出し元が他の(特定の)メソッドを呼び出すと、返されたポインターが無効になる可能性があると言った方が正確でしょうか?

このようなモデルを使用しないことをお勧めしますが、絶対にこれを行う必要があり、APIでこれを強制する必要がある場合は、おそらく2レベルまたは2レベルの間接参照を追加する必要があります。例:実ポインターを含む参照カウントオブジェクトで実オブジェクトをラップします。参照カウントされたオブジェクトのポインタはnull、実際のオブジェクトが削除されたときに設定されます。これは醜いです。(それを行うためのより良い方法があるかもしれませんが、それでも醜いかもしれません。)

于 2010-12-10T04:35:41.187 に答える
2

あなたの質問の2番目の部分に答えるために(「ポインタがいつでも削除される可能性があることをどのように伝えるのですか?」)-

これは危険な行為であり、考慮する必要のある微妙な詳細があります。それは本質的に際どいです。

ポインタをいつでも削除できる場合は、「まだ有効ですか?」にチェックを入れても、別のコンテキストからポインタを使用することは安全ではありません。毎回、チェック後、使用する前に少しだけ削除される場合があります。

これらを行うための安全な方法は、「弱いポインター」の概念です。オブジェクトを共有ポインターとして格納し(1レベルの間接参照、いつでも解放できます)、戻り値を弱いポインターにします。使用する前にクエリを実行し、使用後に解放する必要があります。このように、オブジェクトがまだ有効である限り、それを使用できます。

擬似コード(発明された弱いポインタと共有ポインタに基づいており、Boostを使用していません...)-

weak< Animal > animalWeak = getAnimalThatMayDisappear();
// ...
{
    shared< Animal > animal = animalWeak.getShared();
    if ( animal )
    {
        // 'animal' is still valid, use it.
        // ...
    }
    else
    {
        // 'animal' is not valid, can't use it. It points to NULL.
        // Now what?
    }
}
// And at this point the shared pointer of 'animal' is implicitly released.

しかし、これは複雑でエラーが発生しやすく、おそらくあなたの生活を困難にするでしょう。可能であれば、もっとシンプルなデザインにすることをお勧めします。

于 2010-12-10T05:15:13.273 に答える
1

スライスを回避するには、オブジェクトへのポインタを返すか、渡す必要があります。(参照は基本的に「永続的に逆参照されるポインター」であることに注意してください。

Animal r2 = rFunc();
r2.makeSound();

ここで、r2は(コンパイラーが生成したコピーコンストラクターを使用して)インスタンス化されていますが、Dogパーツは省略されています。このようにすると、スライスは発生しません。

Animal& r2 = rFunc();

ただし、vFunc()関数はメソッド自体の内部でスライスします。

この関数についても説明します。

Animal& rFunc()
{
    return *(new Dog());
}

それは奇妙で安全ではありません。名前のない一時的な変数(逆参照されたDog)への参照を作成しています。ポインタを返す方が適切です。参照を返すことは、通常、メンバー変数などを返すために使用されます。

于 2010-12-10T04:40:19.497 に答える
1

ポインターを返す場合、そのポインターが削除するものではないことをプログラマーにどのように伝えるのですか?または、ポインターがいつでも(同じスレッドから異なる関数から)削除されることを通知して、呼び出し元の関数がポインターを格納しないようにするにはどうすればよいですか。

本当にユーザーを信頼できない場合は、ポインターをまったく与えないでください。整数型のハンドルを返し、Cスタイルのインターフェイスを公開します(たとえば、フェンスの側にインスタンスのベクトルがあります)。 、そして最初のパラメーターとして整数を取り、ベクトルにインデックスを付け、メンバー関数を呼び出す関数を公開します)。これは昔ながらの方法です(「メンバー関数」のような派手なものが常にあるとは限りませんでした;))。

それ以外の場合は、適切なセマンティクスを持つスマートポインターを使用してみてください。正気の人は誰もそれdelete &*some_boost_shared_ptr;が良い考えだとは思わないでしょう。

于 2010-12-10T06:02:42.060 に答える
1

しかし、実際には犬である動物の値を返す関数が必要だとしましょう。

  1. 私が得ることができる最も近いものが参照であることを正しく理解していますか?

はい。それで合っています。しかし、問題は、参照を理解できないほどではなく、C++のさまざまなタイプの変数やC++でのnew動作を理解していないことだと思います。C ++では、変数はプリミティブデータ(int、float、doubleなど)、オブジェクト、またはプリミティブやオブジェクトへのポインター/参照にすることができます。Javaでは、変数はプリミティブまたはオブジェクトへの参照のみになります。

C ++では、変数を宣言すると、実際のメモリが割り当てられ、変数に関連付けられます。Javaでは、newを使用してオブジェクトを明示的に作成し、新しいオブジェクトを変数に明示的に割り当てる必要があります。ただし、ここで重要な点は、C ++では、変数がポインターまたは参照である場合、アクセスに使用するオブジェクトと変数は同じものではないということです。Animal a;とは違うことAnimal *a;を意味します。とは違うことを意味しAnimal &a;ます。これらのいずれにも互換性のあるタイプはなく、互換性もありません。

入力するときはAnimal a1、C++で。新しいAnimalオブジェクトが作成されます。したがって、と入力Animal a2 = a1;すると、2つの変数(a1およびa2)と2つのAnimalオブジェクトがメモリ内の異なる場所に配置されます。両方のオブジェクトの値は同じですが、必要に応じて値を個別に変更できます。Javaでは、まったく同じコードを入力すると、2つの変数が作成されますが、オブジェクトは1つだけになります。どちらの変数も再割り当てしない限り、それらは常に同じ値になります。

  1. さらに、rFuncインターフェースを使用して、返される参照がAnimal&に割り当てられていることを確認する必要がありますか?(または、スライスを介して多型を破棄する動物への参照を意図的に割り当てます。)

参照とポインタを使用すると、オブジェクトを使用する場所にコピーせずに、オブジェクトの値にアクセスできます。これにより、オブジェクトの存在を宣言した中括弧の外側から変更できます。参照は通常、関数パラメーターとして、またはオブジェクトのプライベートデータメンバーを新しいコピーを作成せずに返すために使用されます。通常、参照を受け取ったとき、それを何にも割り当てません。あなたの例を使用すると、によって返される参照を変数に割り当てる代わりに、rFunc()通常はと入力しrFunc().makeSound();ます。

したがって、はい、のユーザーはrFunc()、戻り値を何かに割り当てる場合、それを参照に割り当てる必要があります。理由がわかります。rFunc()によって返される参照をとして宣言された変数に割り当てるAnimal animal_variableと、1つのAnimal変数、1つのAnimalオブジェクト、および1つのオブジェクトになりDogます。Animalに関連付けられているオブジェクトはanimal_variable、可能な限り、Dogからの参照によって返されたオブジェクトのコピーですrFunc()。ただし、その変数はオブジェクトanimal_variableに関連付けられていないため、からポリモーフィックな動作を取得することはできません。を使用して作成したため、参照によって返されたオブジェクトはまだ存在しますがDog、アクセスできなくなり、リークされました。Dognew

  1. 上記のrFuncで行った愚かなことを行わずに、いったいどうやって新しく生成されたオブジェクトへの参照を返すことになっているのでしょうか。(少なくとも、これはばかげていると聞きました。)

問題は、3つの方法でオブジェクトを作成できることです。

{ // the following expressions evaluate to ...  
 Animal local;  
 // an object that will be destroyed when control exits this block  
 Animal();  
 // an unamed object that will be destroyed immediately if not bound to a reference  
 new Animal();  
 // an unamed Animal *pointer* that can't be deleted unless it is assigned to a Animal pointer variable.  
 {  
  // doing other stuff
 }  
} // <- local destroyed

C ++で行うのnewは、メモリ内にオブジェクトを作成することだけです。オブジェクトは、そう言うまで破棄されません。しかし、それを破壊するためには、それがメモリ内のどこで作成されたかを覚えておく必要があります。これを行うには、ポインタ変数を作成し、によって 返された Animal *AnimalPointer;ポインタをに割り当てます。使い終わったときにオブジェクトを破棄するには、と入力する必要があります。new Animal()AnimalPointer = new Animal();Animaldelete AnimalPointer;

于 2010-12-16T22:21:47.940 に答える
0

(動的メモリが参照に入り、メモリリークが発生するという問題は無視しています...)

Animalが抽象的な基本クラスである場合、分割の問題はなくなります。つまり、少なくとも1つの純粋仮想メソッドがあり、直接インスタンス化することはできません。以下はコンパイラエラーになります。

Animal a = rFunc();   // a cannot be directly instantiated
                      // spliting prevented by compiler!

しかし、コンパイラーは以下を許可します:

Animal* a = pFunc();  // polymorphism maintained!
Animal& a = rFunc();  // polymorphism maintained!

したがって、コンパイラはその日を節約します!

于 2010-12-10T04:44:05.153 に答える
0

ポイント1:参照を使用しないでください。ポインタを使用します。

ポイント2:上記のものは、階層分類スキームである分類法と呼ばれます。分類法は、オブジェクト指向モデリングにはまったく不適切な種類の例です。あなたのささいな例は、あなたのベースの動物がすべての動物が音を立てると仮定し、他に面白いことを何もできないためにのみ機能します。

次のような関係を実装しようとした場合

virtual bool Animal :: eats(Animal * other)= 0;

あなたはそれをすることができないことに気付くでしょう。事は:犬は動物の抽象化のサブタイプではありません。タクソノミーの要点は、パーティションの各レベルのクラスに新しい興味深いプロパティがあることです。

例:脊椎動物には背骨があり、それが軟骨でできているのか骨でできているのかを尋ねることができます。無脊椎動物の質問すらできません。

完全に理解するには、Dogオブジェクトを作成できないことを確認する必要があります。結局のところ、それは抽象化ですよね?なぜなら、ケルピーとコリーがあり、個々の犬はいくつかの種でなければなりません..分類スキームは好きなだけ深くすることができますが、具体的な個人をサポートすることはできません。Fidoはではありません、それは彼の分類タグです。

于 2010-12-10T04:45:49.160 に答える
0

メソッドからポリモーフィック型を返したいが、それをヒープに割り当てたくない場合は、それをそのメソッドのクラスのフィールドにし、必要な基本クラスのポインターを返す関数を作成することを検討できます。

于 2013-04-17T21:38:14.980 に答える