Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::reference_wrapper<T> usage in a container

If I could I would remove all raw pointers * from my code, because using them may be not thread safe and intentions of the design are not clear (optional value, ownership, etc). Sometimes however it is not that easy to not use pointers. For example we tend to use pointers for a base type in a container of polymorphic types:

class A : noncopyable { ... };
class B : public A { ... };

std::vector<A*> v;
v.emplace_back(new B);

// temporary container for some operation
std::vector<A*> selected;
if(check())
   selected.emplace_back(v.front());

What can you say about above code? Who is the owner? Is it a shared ownership or not? It is why we should probably do that for v:

std::vector<std::unique_ptr<A>> v;
v.emplace_back(make_unique<B>());

Now it is clear that v owns the objects but I still do not like that selected has a raw pointer and makes my design not intuitive. Looking into Standard C++ library I think that there is only one type that could do the job - std::reference_wrapper:

std::vector<std::unique_ptr<A>> v;
v.emplace_back(make_unique<B>());

// temporary container for some operation
std::vector<std::reference_wrapper<A>> selected;
if(check())
  selected.emplace_back(*v.front());

How do you feel about that code? Is it a good practice? I know that std::ref() and std::cref where meant to primarily work with templates, but it seems that here we can also use it to clearly state our design intent. The only problem I see is that I have to dereference std::reference_wrapper with get() and there are no operator*() or operator->() inside to have the same interface like in a container with unique_ptr. Should I write something similar on my own? Or maybe a reference_wrapper could be extended for such use case in future C++ versions? Please share your feedback.

EDIT: I changed the code samples to maybe better show the intent.

like image 909
Mateusz Pusz Avatar asked Dec 14 '12 07:12

Mateusz Pusz


2 Answers

You have already provided a solution which looks sound. I understand that the question is "How do you feel?"

My personal feeling is that there need to exist some balance between safety and unambiguity on the one hand and the simplicity of the code on the other. It looks like your solution may be pushing it too hard towards safety and compromising the simplicity too much. Whenever I used containers holding "weak references" I used raw pointers to represent these. True, this might make it less clear who the owner of the object is, but it has some advantages too: you do not have to study what a "reference_wrapper" is, and the code is clear. If you use them (a container of weak references) only temporarily and you encapsulate this usage, the ownership issue should be minimal.

But this is just a question of personal preference, I guess. Let me just propose using different types for the same purpose. This is provided that you can afford to use Boost. For "strong" references (which own the resource) you could use Steve Watanabe's Type Erasure library. It does not require an explicit usage of free-store memory, and i suppose for small types it can get away from using heap-memory altogether (using small-buffer optimization). It has been recently accepted to Boost, although has not been release yet, I think.

For weak references, consider using "optional references" with Boost.Optional:

int i = 0;
boost::optional<int&> oi = i; // note: int&
i = 2;
assert(*oi == 2);

It has same semantics as reference_wrapper.

like image 64
Andrzej Avatar answered Nov 03 '22 02:11

Andrzej


I think calling them shared_ptr's is not logically wrong. However, looking at the definition of std::weak_ptr:

std::weak_ptr is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by std::shared_ptr. It must be converted to std::shared_ptr in order to access the referenced object.

it might be a better candidate. At least when you are fiddling with the pointer through selected you will need to assume temporary ownership. Since the original pointer is stored in a shared pointer, using weak pointer will be safer.

like image 24
perreal Avatar answered Nov 03 '22 01:11

perreal