0

これは、私が理解していない奇妙な行動の 1 つです。

私はリストを持つクラスとそれにゲッターを持っています:

class A
{
  private:
   std::list<OtherClass *> l;
  public:
   std::list<OtherClass *> getL()
   {
     return l;
   }
}

次に、次のようなことをすると:

A inst;
std::list<OtherClass *>::iterator itB = inst.getL().begin();
std::list<OtherClass *>::iterator itE = inst.getL().end();
for (; itB != itE; ++itB) // Instant ABORT !

しかし、もしそうなら:

A inst;
std::list<OtherClass *> l = inst.getL();
std::list<OtherClass *>::iterator itB = l.begin();
std::list<OtherClass *>::iterator itE = l.end();
for (; itB != itE; ++itB) // It works now !

誰かが私になぜこれが起こっているのか説明できますか? 中止しないために、このような一時変数を使用する必要があるのはなぜですか? 前もって感謝します !

4

6 に答える 6

5

他のすべての回答は戻り値として変更可能な参照を示唆していますが、私はそれらをconstにします:

const std::list<OtherClass *> &getL() const;

また、関数自体を const にしました。つまり、オブジェクト自体を変更しません。したがって、正しいgetter メソッドが得られます(オブジェクトを変更したり、変更可能な参照を返したりする必要はありません)。

属性を変更できるgetter 関数の 2 番目のバージョンを導入したい場合があります (属性が変更された場合に実行する必要のあるコードを非表示にしたくない場合は、関連するものの更新など)。

std::list<OtherClass *> &getL();

ただし、既に示したように、場合によっては、このバージョンが必要なものではありません。セッターメソッドで何かをしなければならない場合、その属性の変更可能な参照を公開したくないでしょう。呼び出し元は、上記のように getter を呼び出し、値を変更して setter を呼び出す必要があります。ただし、リスト、ベクター、マップなどの大きなデータ構造の場合、これは遅くなる可能性があるため、次のような単一要素セッターを導入することをお勧めします。setLAt(int index, OtherClass *value);

于 2013-01-19T23:21:55.863 に答える
3

これまでのすべての回答は、それを正しく行う方法を示していますが、コードが機能しない理由をさらに詳しく説明したいと思います。他の人が指摘したように、あなたの「ゲッター」はリストを値で返しています。これは (主に) C++ に固有のものです。プログラマーは、オブジェクトを値渡しするか参照渡しするかを明示的に指定する必要があります。Java などの他のプログラミング言語は、(ほとんど) 常に参照渡しになります。次のように変数を割り当てるとします。

MyClass a;
MyClass b = a;

多くの言語では、割り当ては次のことを意味します。 をb指す参照を作成しaます。その後、 でメソッドを呼び出すことができb、 であるかのように動作しますa

一方、C++ では、次のことを意味しbます。これは、すべての要素が新しく作成されたリストにコピーされることを意味します! (これは、他の影響に加えて、パフォーマンスの問題になる可能性があります)。ab

一方、コンパイラに a への参照を作成するように指示した場合:

MyClass& b = a;

次に、これbは実際に であるかのように動作します。状態はコピーされず、変更bすると変更されますa

さて、コード例に戻りましょう。最初のバージョンでは、次の行があります。

// Creates an invalid iterator!
std::list<OtherClass *>::iterator itB = inst.getL().begin();

それは実際にはそこにあるさまざまなものの束です。を呼び出すとinst.getL()、新しいリストが作成され、instのリスト メンバーのすべての内容がそのリストにコピーされます。次に、そのコピーへの反復子を取得します。その後、コピー自体が破棄され、イテレータが無効になります。なんで?リストのコピーを何にも割り当てていないためです。C++ では、newスコープ外になるスタック割り当てオブジェクト (つまり、 を使用して作成されていない) は破棄されます。簡単に言えば、「スコープ外」は、オブジェクトがその名前でアクセスできなくなるとすぐに発生します。

{ // Begin scope
    MyClass o; 
    // Inside the braces, it's possible to refer to o:
    o.doSomething();
} // End scope
o.doSomething() // Will be an error, as o is not "known" anymore

これは、次のように関数の戻り値を破棄した場合にも発生します。

inst.getL(); 

これにより、リストのコピーが作成され、再び破棄されます。

では、なぜ 2 番目の例が機能するのでしょうか。リストのコピーを一時変数に割り当てるため、それらはスコープ内にとどまります。

std::list<OtherClass *> l = inst.getL();

「getter」呼び出しからの一時オブジェクトはl(今のところ代入演算子、RVO などを無視して) に格納され、取得されたすべてのイテレータは範囲外にlなるまで有効になります。l

std::list<OtherClass *>::iterator itB = l.begin(); // valid

したがって、おそらく期待どおりではありませんが、これは機能します。イテレータは、実際のデータではなく、リストのコピーを操作しています。これは時々あなたが望むものかもしれませんが、あなたの場合、他の回答で示唆されているように参照が必要です。

これがあなたのためにそれを少し片付けるのに役立ったことを願っています.

于 2013-01-19T23:46:10.740 に答える
2

基になるリストへの参照を返すようにゲッターを変更します。

std::list<OtherClass *> &getL()
                        ^

アンパサンドがないと、呼び出すたびにリストのコピーが返されます。その結果、異なるリストからの反復子にitBなります。itEさらに、これら 2 つのリストは一時的なものであり、forループが始まるまでに破棄されます!

使用する場合にこれを一致lさせるには、参照変数も作成する必要があります。

std::list<OtherClass *> &l = inst.getL();
于 2013-01-19T23:19:11.567 に答える
1

更新してみてください:

std::list<OtherClass *> getL()

std::list<OtherClass *>& getL()

あなたに注意して、毎回リストの新しいコピーを返しinst.getL().begin();ますinst.getL().end();

于 2013-01-19T23:19:27.773 に答える
1

呼び出すたびgetL()に新しいlistものが作成され、返された値の情報がコピーされるためです。

std::list<OtherClass *>::iterator itE = inst.getL().end();

また、このように呼び出すとlist、行末で一時的に破壊されることにも注意してください。イテレータを無効にします。

于 2013-01-19T23:19:30.117 に答える
1

ゲッターはリストのコピーを返します。リストは行末で終了し、反復子は無効です。

おそらく、既存のリストへの参照を返すつもりです。

std::list<OtherClass *> & getL() { return l; }
//                     ^^^
于 2013-01-19T23:19:54.573 に答える