2016-11-29 66 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引用你不能修改的对象。