4

I am having a quite common problem. I have a class that must store a non-owning pointer to a different class object.

I know that:

  • The lifetime of the reference object is guaranteed to outlive the instance.
  • The referenced object is passed in a constructor and does not change with the exception of moves or assignment.
  • It is never invalid.
  • It is used in many methods.
  • It can be shared by many instances.

Think of e.g. a logger class which is not global.

These points lead me to this solution using a reference variable which guarantees validity:

struct Foo{};

struct Bar{
    Bar(Foo& foo):m_foo(foo){}

Foo& m_foo;
};

The big downside is Bar is unnecessarily almost immutable - no assignment, no move.

The usual thing I did was to store Foo as a pointer instead. This solves most of the issues except that it is no longer very clear that the pointer is always valid. Furthermore it adds a small new issue that it can be invalidated in any method, which should not happen. (Making it const has the same downside as &). That makes me add assert(m_foo) to every method for peace of mind.

So, I was thinking about just storing std::reference_wrapper<Foo>. It is always valid and it keeps Bar mutable. Are there any downsides compared to a simple pointer? I know that any method can still point it to e.g. a local variable but let's say that does not happen because it is perhaps hard to obtain a new valid instance of Foo. At least it is harder than simple =nullptr;

I know this approach is used for containers like std::vector so I assume it is okay, but I would like to know if there is any catch I should look for.

Boann
  • 48,794
  • 16
  • 117
  • 146
Quimby
  • 17,735
  • 4
  • 35
  • 55
  • 1
    Possible downsides would be some kind of performance or storage overhead, but it seems there is none: https://stackoverflow.com/questions/46957595/cost-of-unwrapping-a-stdreference-wrapper – Sven Nilsson Nov 25 '20 at 14:56
  • @SvenNilsson Thank you, I missed that one. – Quimby Nov 25 '20 at 15:44

1 Answers1

1

Since Foo is a struct you need to invoke get() every time you access any member or field of Foo. With reference or pointer you could use '.' or '->' respectively for member access. So reference_wrapper is not "transparent" in that regard. (There is currently also no way of making it "transparent" in C++, which would be nice of course).

There will be no runtime overhead but code will be congested with get() calls.

If that is not of a concern for you then there are no downsides of using reference_wrapper instead of a pointer. (In fact reference_wrapper is implemented by using a pointer member)

EDIT: also if you only need to call one or two member functions of Foo one could inherit from reference_wrapper and add a call stub. But that perhaps may be overkill ...

Andreas H.
  • 5,557
  • 23
  • 32
  • Thank you for the answer. I did not know about needing `get()`, that's a bummer. I expected it to work like e.g. `std::unique_ptr`. Could you please expand on why is not possible to get such behaviour? Why cannot be `operator->` misued to return `T*`? Is this about non-overloadability of ``? – Quimby Nov 25 '20 at 15:41
  • 1
    Yes, it's about `.` not being overloadable and `*`/`->` not being appropriate for a non-pointer. And congestion with `get()` is reduced by leveraging implicit contextual conversion to `T&`. – underscore_d Nov 25 '20 at 15:45
  • @underscore_d Thanks, although I believe `->` would be no less valid than `<<` is for streams - you get used to it. Yes, `get()` is unnecessary when passing `Foo` around, but calling its methods needs it and that is what I will be mostly doing :/ – Quimby Nov 25 '20 at 15:57
  • 2
    Well, maybe one day the proposal(s) for `operator .` will get somewhere! – underscore_d Nov 25 '20 at 16:09
  • 1
    @Quimby Overloading '->' would work, but then perhaps one could just use a pointer in the first place. Operator '.' is not overloadable, there have been proposals (even by Stroustrup) but were not succeesful. In case you only need to call a member function for 'Foo' you could inherit from `reference_wrapper` and add a stub member. This will not work for data members though. – Andreas H. Nov 25 '20 at 16:36
  • 1
    Ok, thank everyone for help, I will just stick to a pointer for a while. – Quimby Nov 25 '20 at 16:42
  • @underscore_d Yes, that would be nice. C++ needs full support for the proxy pattern! – Andreas H. Nov 25 '20 at 16:43
  • 1
    @Quimby Understandable, I would use pointer too :-) – Andreas H. Nov 25 '20 at 16:43
  • 1
    @Quimby I don't see the downside of overloading -> and using it instead of get() - I think it is no less intuitive than if you were using a pointer but it is always pointing to a valid object so for me it is the best of both. Why do you think a pointer is better? – Jerry Jeremiah Nov 25 '20 at 20:40
  • Alternatively use a private pointer `Foo* m_foo;` and provide a function `Foo& foo() { return *m_foo; }` (plus a `const` overload, most likely) to both use a pointer and prevent external actors from changing the pointer itself, without needing to mess around with `reference_wrapper`. – N. Shead Nov 25 '20 at 20:57