Passing std::unique_ptr as const ref

Started by
28 comments, last by Juliean 7 years, 3 months ago

Either I'm confused, or I'm seeing a lot of confused responses.

There is nothing wrong with passing objects by reference to const at almost any time, regardless of the storage duration of that object (static, automatic, or dynamic held by a std::unique_ptr). It's in fact an excellent way to structure your code and I'd recommend it above all others since it's clearer, simpler, and idiomatic.

Second best would be passing a naked pointer to const, but then for the static and automatic cases you'd have to decorate the calls with pointer-of operators and call the .get() member function on smart pointers, and you'd lose all of the advantages introduced to the language by references in the first place. Whether to use naked pointers to const or references to const is a design choice most often based on the use of null pointers for in-band signalling of special cases, something that can often be better handled through superior design that follows the SRP.

So to the original post, yes, that's the right thing to do.

Stephen M. Webb
Professional Free Software Developer

Advertisement
I haven't been keeping up with C++ best practices in the last few years, but it would seem to me that taking a const reference to std::unique_ptr<Foo> is confusing.

I would expect most of the time you just want to pass around a pointer or reference to Foo, and the ownership stays with the caller. There will be some exceptional circumstances where you want to transfer ownership of the pointer to the function, which is when I you pass an std::unique_ptr<Foo>. But what is the intent when you pass a `std::unique_ptr<Foo> const &'? I really don't know.

So to the original post, no, because the intent is not clear.
But what is the intent when you pass a `std::unique_ptr const &'? I really don't know.

I could see it as a way of stating "This pointer is not supposed to outlife the function, so it should never be stored", but its really not clear at all, and even when used throughout the codebase, would still be ambiguous.

I totally agree with the notion, that if you don't care about ownership at a certain point, you just pass plain pointers/references. If you ie. have a composite object, where all leaves have a backpointer to their owner, there is no need to have the leaves know about the actual storage type of their owning composite. The composite will outlive the leaves, so each leave gets a Composite*. The additional benefit is that now composites can be stored however they want - as a unique_ptr, shared_ptr (if some code requires it), or some custom memory managment scheme - the code will still work. Whereas if you force "void setParent(const std::unique_ptr<Composite>& parent)", it would mean that the parent would have to be a unique_ptr all the time. An additional bonus of not using unique_ptr through your interface, if you will.

There will be some exceptional circumstances where you want to transfer ownership of the pointer to the function, which is when I you pass an std::unique_ptr.

TBH, I found that quite commonly. Ok, I don't use custom allocators throughout my codebase, but when you just replace every regular occurence of "new" with "std::make_unqiue", you'll often call a function with eigther "std::unique_ptr<> func()" if you want to create a new object, or "void func(std::unique_ptr<>&& ptr)" if you want to set an object. (IDK if std::unique_ptr could or would still be used if you used a complete memory allocation managment scheme, but since I have little experience with it, take my opinion with a grain of salt).

I haven't been keeping up with C++ best practices in the last few years, but it would seem to me that taking a const reference to std::unique_ptr<Foo> is confusing.

I would expect most of the time you just want to pass around a pointer or reference to Foo, and the ownership stays with the caller. There will be some exceptional circumstances where you want to transfer ownership of the pointer to the function, which is when I you pass an std::unique_ptr<Foo>. But what is the intent when you pass a `std::unique_ptr<Foo> const &'? I really don't know.

So to the original post, no, because the intent is not clear.


See, that's where I'm confused because I see nowhere where @cozzie asked that, only where they asked about passing a *std::unique_ptr<T> as an argument to a T const& parameter.

Perhaps cozzie can clarify his question more with a small code example?

Stephen M. Webb
Professional Free Software Developer

Just for future reference, when adding .get() in the sample case above, you still have to use * to identify the pointer. So: *myPtr.get()

This is weird, you shouldn't need to do that.

The idea is that you wrap an object in a std::unique_ptr<> to express it has a single owner at all times.

One object has that std::uniqe_ptr as object. That object owns the wrapped object. It can do "*" and "->" operations, just asif the std::unique_ptr<> object is a pointer to the wrapped object, ie


class X {
  std::unique_ptr<Y> mine;

  void q() {
      ...
      mine->f();  // Access the 'f' function of the 'Y' object.

For other (non-owning) objects, you give out bare pointers


Y* X::getMine() { return mine.get(); }

this means they use the wrapped object, but do not own it.

They do something like


void Z::g(X &x) {
    Y* y_ptr = x.getMine();
    y_ptr->f();
    ....
}

So yeah, you do get a double "*" indirection, but never on the pointer you ".get()" on, as it doesn't live in Z, it lives in X.

Just for future reference, when adding .get() in the sample case above, you still have to use * to identify the pointer. So: *myPtr.get()

This is not correctly stated. You need to use the unary * operator here to dereference the pointer returned by get() (not "identify," that makes no sense). This is because you're using the expression as an argument to a function call where a reference, not a pointer, is expected. This is not necessary if you are using the result of get() as an argument to a function where a pointer is expected.

I haven't been keeping up with C++ best practices in the last few years, but it would seem to me that taking a const reference to std::unique_ptr<Foo> is confusing.

I would expect most of the time you just want to pass around a pointer or reference to Foo, and the ownership stays with the caller. There will be some exceptional circumstances where you want to transfer ownership of the pointer to the function, which is when I you pass an std::unique_ptr<Foo>. But what is the intent when you pass a `std::unique_ptr<Foo> const &'? I really don't know.

So to the original post, no, because the intent is not clear.


See, that's where I'm confused because I see nowhere where @cozzie asked that, only where they asked about passing a *std::unique_ptr<T> as an argument to a T const& parameter.

Perhaps cozzie can clarify his question more with a small code example?


Upon re-reading the original post, I think you are correct. A few lines of code to clarify would be great.

Yup, it appears I interpreted the question to mean a const reference to the std::unique_ptr, as opposed to that of the owned object.

Still an interesting subject to discuss :)

OK, time for some more context and clarification :rolleyes:

- I'm practicing and applying 'abstraction' of my low-level renderer

- one example: I've created a class IRenderTarget (parent), which has an inherited DX11RenderTarget class

- in the low-level renderer there's a member: std::unique_ptr<IRenderTarget> mTempRT;

- because I'm using dx11 as API: I'm creating a DX11RenderTarget:


	mTempRT = std::move(std::make_unique<DX11RenderTarget>(mDevice.get(), mSettings.GetScreenWidth(), mSettings.GetScreenHeight(), mBackBufferFormat, mSettings.GetMSAANrSamples(), mSettings.GetAAQuality(), false));

- now the low-level renderer also has a member std::unique_ptr<IDevice>, which in the same way is created as a DX11 device (inherited class):


	mDevice = std::move(std::make_unique<DX11Device>(pHwnd, &mSettings, mBackBufferFormat, mDepthBufferFormat));

- the IDevice class has a number of virtual functions, inherited in the DX11Device class

- for example, one to set the current RenderTarget:


	bool SetRenderTarget(const IRenderTarget &pRT);

- this member function shouldn't do anything non-const with the rendertarget, so I decided to let the parameter be a const ref

- and to pass my mTempRT to the Device/ this function, I call it like this:


	if(!mDevice->SetRenderTarget(*mTempRT.get())) return false;

I hope this gives some clarification.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

maybe I'm missing something but why do you wrap the result of make_unique in std::move above? The result of make_unique is already an rvalue, so the std::move is redundant.

Back to the topic at hand, I happen to agree with the rules written here by Stroustrup and Sutter: http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-in

F.16: For “in” parameters, pass cheaply-copied types by value and others by reference to const

So you're doing it right, and what you are communicating through this function signature (const IRenderTarget &pRT) is:

1) this method has no interest in the ownership of the object being passed, and does not sink or re-seat the pointer, otherwise you would pass (unique_ptr<IRenderTarget>), see rule F.18

2) this is an "in" parameter, otherwise you would have used (IRenderTarget&) or (IRenderTarget*)

3) it cannot be passed no value (aka nullptr), otherwise you would have used (const IRenderTarget* pRT)

This is a nitpick and a matter of personal preference but I would not prefix a const ref parameter name with "p".

On a slight tangent, I would prefer to sink those objects in to member variables via constructor parameters, dependency injection style, rather than calling "make_unique" within the Renderer's constructor.

This topic is closed to new replies.

Advertisement