2016-11-29 47 views
0

我在一段時間後再次學習C++,並且有一個理解const引用作爲返回值的問題。所以這是我的了:一類,Foo,其持有std::list作爲成員:作爲返回值的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,這是罰款。現在我想找回這個名單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 

現在,這令我感到奇怪和利茲我兩個問題:

  1. 由於第二cout回報3一遍,致電clear()顯然不會修改原來的foo.mList對象。但是,如果我返回了一個參考文獻,爲什麼會這樣呢?退貨時是否有複印件?
  2. 如果我收到const(!)引用,爲什麼我首先允許撥打l.clear()
+3

您需要聲明l變量作爲參考。否則,它只是一個副本。 –

+0

'std :: list l = foo.getList();'創建列表的副本。 'const std :: list &l = foo.getList();'獲取列表的const引用。 – zneak

回答

5

getList返回const&到列表中。

你可以做的一件事,如果你選擇,與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; } 

讓你保證範圍爲常量:

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_list返回在可在for(:)迴路中使用的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 idiom來執行遍歷 - 刪除操作的速度會更快。有些情況下列表是正確的答案,但依賴持久性迭代器/指針/引用不僅僅是在中間性能中刪除/添加。 – Yakk

4

當你做std::list<int> l = foo.getList();,通過複製返回的引用創建一個新的列表l(即使getList()返回const參考,它不禁止的,如果你想將它複製)。所以它以後可以修改你製作的這個副本。

如果你做const std::list<int>& l = foo.getList();那麼沒有複製和l仍然是一個const引用你不能修改的對象。