2016-11-29 3 views
0

私はしばらくしてからC++を習得しており、戻り値としてconstの参照を理解する問題があります。メンバーとしてstd::listを保持しているクラス、Fooを、::だから、ここで私が持っているものである戻り値としてのconst参照はC++でどのように機能するのですか

class Foo 
{ 
    std::list<int> mList; 
    Foo() { mList.insert(mList.end(), { 1, 2, 3 }); } 
    size_t size() const { return mList.size(); } 
}; 

は、クラスFooからfooオブジェクトを作成し、細かいfoo.size()戻り3を呼び出します。今、私は2つの要件を、このリストmListを取得したい:

  • 私は、呼び出し元が元のリストを変更できるようにする必要はありません。
  • リストを取得するときに各リストメンバーのコピーを作成したくありません。

この話題を少し読んだ後、私はconstのリストへの参照を返すことにしました。したがって、私はFooに次のメソッドを追加しました:

const std::list<int>& getList() const { return mList; } 

を私は期待私はこのリストの中に、元のデータにアクセスできるように、これは、リストmListへの参照を返すというものでした。しかし、それはconstの参照であるため、返されたリスト/参照を変更することもできないと予想しました。

Foo foo; 
cout << foo.size() << endl; // returns 3 
std::list<int> l = foo.getList(); 
l.clear(); 
cout << foo.size() << endl; // returns 3 again 

は今、この2つの質問に、私とリーズ私を驚か:再び第二cout戻り3以来

  1. しかし、このビットで遊んで私は、次のを発見しましたclear()への呼び出しは、明らかに元のfoo.mListオブジェクトを変更しません。しかし、なぜ私は参照を返した場合、その場合ですか?帰りの間にコピーが起こっていますか?

  2. const(!)のリファレンスを受け取った場合、最初にl.clear()に電話することが許可されるのはなぜですか?あなたがstd::list<int> l = foo.getList();を行うと、あなたが返された参照を(getList()const参照を返す場合でも、あなたがしたい場合は、それをコピーすることは禁じられていない)コピーして、新しいリストlを作成
+3

l変数を参照として宣言する必要があります。それ以外の場合は単なるコピーです。 –

+0

'std :: list l = foo.getList();'リストのコピーを作成します。 'const std :: list &l = foo.getList();'はリストへのconst参照を取得します。 – zneak

答えて

5

getListは、const&をリストに返します。

あなたが選ぶことができる1つのことは、リストにconst&をコピーすることです。

std::list<int> l = foo.getList(); 

ここではコピーを選択します。 std::list<int> lを参照してください。それは参考にはならない。それがオブジェクトです。

const&を割り当てることで、リストの内容をローカル変数にコピーしてください。

このローカル変数を使用すると、簡単に値と同様に扱うことができない何かをしたい場合は、ビュータイプを書き込むことができます

など、クリア編集し、変更することができます。

template<class It> 
struct range_view_t { 
    It b; It e; 
    It begin() const { return b; } 
    It end() const { return e; } 
}; 
template<class C, 
    class It = decltype(std::begin(std::declval<C&>())) 
> 
range_view_t<It> range_view(C& c) { 
    return {std::begin(c), std::end(c)}; 
} 

ここで、range_view(some_list)は、リストの繰り返し可能な範囲を返します。

template<class X> 
X const& as_const(X& x) { return x; } 

あなたは範囲を確保することができますconstのです:

auto r = range_view(as_const(list)); 

はあなたのリストにイテレータの読み取り専用範囲を提供します。

template<class C> 
using range_over = decltype(range_view(std::declval<C&>())); 
template<class C> 
using const_range_over = range_over<const C>; 

class Foo 
{ 
    std::list<int> mList; 
    Foo() { mList.insert(mList.end(), { 1, 2, 3 }); } 
    size_t size() const { return mList.size(); } 
    const_range_over<std::list<int>> 
    get_list() const { 
    return range_view(mList); 
    } 
}; 

get_listfor(:)ループで使用することができるmListにおける範囲ビューを返します。 get_listの戻り値のコピーを保存すると、何かのビューをコピーしているだけなので、何もコピーされません。

ほとんどの場合、autoは便利です。これは、範囲表示の種類がユーザーにとって興味がないためです。

auto l = foo.get_list(); 

l.clear()方法を持っていません。しかし

for(auto&& x : l) 
    std::cout << x << "\n"; 

:あなたはこれを行うことができます。

最後に、std::listは、ほとんどの場合、説明できる問題の解決策として間違っています。

+1

最後のステートメントに同意しないでください。真ん中に挿入して参照を無効にしないコンテナを使用するのは珍しいことではありません。 – SergeyA

+0

お返事ありがとうございました。私は答えとしてそれを受け入れます、なぜ、 'std :: list'はほとんど常に間違った解決策ですか?私は頻繁にリストに/からメンバーを挿入したり削除したりするつもりであり、特定のインデックスのメンバーにアクセスする必要はありません。リンクされたリストのユースケースではありませんか? – Matthias

+0

@matt nope。トラバーサル(アイテムを見つける)は非常に遅く、 'std :: list'を使うとどんな利点もありません。ポインタの安定性が不要で、最後に 'std :: vector'を追加すると、erase-removeイディオムを使ってトラバーサル削除を行うと面倒な処理が高速になります。リストが正解であるケースがありますが、中間のパフォーマンスでの削除/追加以上の永続イテレータ/ポインタ/リファレンスに依存するケースがあります。 – Yakk

4

。それで、あなたが作ったこのコピーを後で修正することが許されます。

const std::list<int>& l = foo.getList();を実行した場合は、コピーが作成されず、lは変更できないオブジェクトへの参照が残ります。

関連する問題