How to Pass Std::Unique_Ptr into a Function

How can I pass std::unique_ptr into a function

There's basically two options here:

Pass the smart pointer by reference

void MyFunc(unique_ptr<A> & arg)
{
cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
MyFunc(ptr);
}

Move the smart pointer into the function argument

Note that in this case, the assertion will hold!

void MyFunc(unique_ptr<A> arg)
{
cout << arg->GetVal() << endl;
}

int main(int argc, char* argv[])
{
unique_ptr<A> ptr = unique_ptr<A>(new A(1234));
MyFunc(move(ptr));
assert(ptr == nullptr)
}

Cannot pass std::unique_ptr in std::function

The std::function requires the function object to be Copy-Constructible, so you can't expect a lamdba to be moved to it. On initialization, it attempts to copy the lambda and so the std::unique_ptr with it, which is a member of this lambda, and, expectedly, fails to do so. What you can do is store your lambda in a variable and pass it to function that accepts const std::function& using std::ref like that:

        void foo(const std::function<void()>& f); // function declaration
auto a = [h = std::move(handle)]() mutable
{
std::cout << *h << std::endl;
};
foo(std::ref(a));

This is a related question with much more detailed answers: How to create an std::function from a move-capturing lambda expression?

How do I pass a unique_ptr argument to a constructor or a function?

Here are the possible ways to take a unique pointer as an argument, as well as their associated meaning.

(A) By Value

Base(std::unique_ptr<Base> n)
: next(std::move(n)) {}

In order for the user to call this, they must do one of the following:

Base newBase(std::move(nextBase));
Base fromTemp(std::unique_ptr<Base>(new Base(...));

To take a unique pointer by value means that you are transferring ownership of the pointer to the function/object/etc in question. After newBase is constructed, nextBase is guaranteed to be empty. You don't own the object, and you don't even have a pointer to it anymore. It's gone.

This is ensured because we take the parameter by value. std::move doesn't actually move anything; it's just a fancy cast. std::move(nextBase) returns a Base&& that is an r-value reference to nextBase. That's all it does.

Because Base::Base(std::unique_ptr<Base> n) takes its argument by value rather than by r-value reference, C++ will automatically construct a temporary for us. It creates a std::unique_ptr<Base> from the Base&& that we gave the function via std::move(nextBase). It is the construction of this temporary that actually moves the value from nextBase into the function argument n.

(B) By non-const l-value reference

Base(std::unique_ptr<Base> &n)
: next(std::move(n)) {}

This has to be called on an actual l-value (a named variable). It cannot be called with a temporary like this:

Base newBase(std::unique_ptr<Base>(new Base)); //Illegal in this case.

The meaning of this is the same as the meaning of any other use of non-const references: the function may or may not claim ownership of the pointer. Given this code:

Base newBase(nextBase);

There is no guarantee that nextBase is empty. It may be empty; it may not. It really depends on what Base::Base(std::unique_ptr<Base> &n) wants to do. Because of that, it's not very evident just from the function signature what's going to happen; you have to read the implementation (or associated documentation).

Because of that, I wouldn't suggest this as an interface.

(C) By const l-value reference

Base(std::unique_ptr<Base> const &n);

I don't show an implementation, because you cannot move from a const&. By passing a const&, you are saying that the function can access the Base via the pointer, but it cannot store it anywhere. It cannot claim ownership of it.

This can be useful. Not necessarily for your specific case, but it's always good to be able to hand someone a pointer and know that they cannot (without breaking rules of C++, like no casting away const) claim ownership of it. They can't store it. They can pass it to others, but those others have to abide by the same rules.

(D) By r-value reference

Base(std::unique_ptr<Base> &&n)
: next(std::move(n)) {}

This is more or less identical to the "by non-const l-value reference" case. The differences are two things.

  1. You can pass a temporary:

    Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
  2. You must use std::move when passing non-temporary arguments.

The latter is really the problem. If you see this line:

Base newBase(std::move(nextBase));

You have a reasonable expectation that, after this line completes, nextBase should be empty. It should have been moved from. After all, you have that std::move sitting there, telling you that movement has occurred.

The problem is that it hasn't. It is not guaranteed to have been moved from. It may have been moved from, but you will only know by looking at the source code. You cannot tell just from the function signature.

Recommendations

  • (A) By Value: If you mean for a function to claim ownership of a unique_ptr, take it by value.
  • (C) By const l-value reference: If you mean for a function to simply use the unique_ptr for the duration of that function's execution, take it by const&. Alternatively, pass a & or const& to the actual type pointed to, rather than using a unique_ptr.
  • (D) By r-value reference: If a function may or may not claim ownership (depending on internal code paths), then take it by &&. But I strongly advise against doing this whenever possible.

How to manipulate unique_ptr

You cannot copy a unique_ptr. You can only move it. The proper way to do this is with the std::move standard library function.

If you take a unique_ptr by value, you can move from it freely. But movement doesn't actually happen because of std::move. Take the following statement:

std::unique_ptr<Base> newPtr(std::move(oldPtr));

This is really two statements:

std::unique_ptr<Base> &&temporary = std::move(oldPtr);
std::unique_ptr<Base> newPtr(temporary);

(note: The above code does not technically compile, since non-temporary r-value references are not actually r-values. It is here for demo purposes only).

The temporary is just an r-value reference to oldPtr. It is in the constructor of newPtr where the movement happens. unique_ptr's move constructor (a constructor that takes a && to itself) is what does the actual movement.

If you have a unique_ptr value and you want to store it somewhere, you must use std::move to do the storage.

How to pass raw pointer of unique_ptr to a function that takes in unique_ptr?

instead of passing the address with get() you must release the ownership with release()

void api_fun(std::unique_ptr<Res> const&);

void fun2(std::unique_ptr<Res>& uniq_ptr){
api_fun(uniq_ptr);
std::cout << uniq_ptr.get() << '\n';
}

void fun1(Res* ptr){
std::unique_ptr<Res> tt(ptr);
fun2(tt);
tt.release();
}

int main()
{
std::unique_ptr<Res> up(new Res("Hello, world!"));
auto p = up.release();
fun1(p);
up.reset(p);
std::cout << "All good" << std::endl;
}

but these fun1 and fun2 are not fun at all for anyone who is going to work with it later ;)

Surprisingly it looks exception-safe.

Passing unique_ptr to functions

In Modern C++ style, there are two keys concepts:

  • Ownership
  • Nullity

Ownership is about the owner of some object/resource (in this case, an instance of Device). The various std::unique_ptr, boost::scoped_ptr or std::shared_ptr are about ownership.

Nullity is much more simple however: it just expresses whether or not a given object might be null, and does not care about anything else, and certainly not about ownership!


You were right to move the implementation of your class toward unique_ptr (in general), though you may want a smart pointer with deep copy semantics if your goal is to implement a PIMPL.

This clearly conveys that your class is the sole responsible for this piece of memory and neatly deals with all the various ways memory could have leaked otherwise.


On the other hand, most users of the resources could not care less about its ownership.

As long as a function does not keep a reference to an object (store it in a map or something), then all that matters is that the lifetime of the object exceeds the duration of the function call.

Thus, choosing how to pass the parameter depends on its possible Nullity:

  • Never null? Pass a reference
  • Possibly null? Pass a pointer, a simple bare pointer or a pointer-like class (with a trap on null for example)

C++: How to pass the object from a unique_ptr into a function by value?

The problem is that you don't load the file the same way you write it. If you write it using smart pointers, then the file contains additionnal data including a node polymorphic_id. You would have a file similar to this one:

<?xml version="1.0" encoding="utf-8"?>
<cereal>
<value0>
<polymorphic_id>2147483649</polymorphic_id>
<polymorphic_name>Rectangle</polymorphic_name>
<ptr_wrapper>
<valid>1</valid>
<data>
<length>1</length>
<width>5</width>
</data>
</ptr_wrapper>
</value0>
<value1>
<polymorphic_id>1</polymorphic_id>
<ptr_wrapper>
<valid>1</valid>
<data>
<length>2</length>
<width>25</width>
</data>
</ptr_wrapper>
</value1>
</cereal>

after modifying the registration to use

 CEREAL_REGISTER_TYPE_WITH_NAME(ClassRectangle, "Rectangle")

instead of:

 CEREAL_REGISTER_TYPE(ClassRectangle)

The code I used to write the file is:

    std::ofstream is("output.xml");
cereal::XMLOutputArchive arr(is);

auto *rp1 = new ClassRectangle();
rp1->length = 1;
rp1->width = 5;
std::unique_ptr<SuperParentClass> p1(rp1);

auto *rp2 = new ClassRectangle();
rp2->length = 2;
rp2->width = 25;
std::unique_ptr<SuperParentClass> p2(rp2);

arr(p1);
arr(p2);

Then that file can be read back using smart pointers to SuperParentClass.

Alternatively, if you want to read the original file in a smart pointer, it can easily be done by using the following lines:

    std::unique_ptr<ClassRectangle> UPSPC(new ClassRectangle());
arr(*UPSPC);
UPSPC->run();

Obviously, you have to use the derived type here because the file was saved without hierarchical information needed to handle polymorphic type.

Also notice the * in the expression arr(*UPSPC). That way, you are loading into an already allocated object.

Update

If you have at most one obect of each derived type, then you could load them by name:

    std::unique_ptr<ClassTriangle> tri(new ClassTriangle());
std::unique_ptr<ClassRectangle> rect(new ClassRectangle());

arr(cereal::make_nvp("Triangle", *tri));
arr(cereal::make_nvp("Rectangle", *rect));

rect->run();
tri->run();

Passing std::unique_ptr to a function by using a variable that is already created using new X()

In the first case, this->handler_ ends up containing a pointer that it does not own (the unique_ptr owns it, and will delete it when the unique_ptr is destroyed unless it's extracted from the unique_ptr manually). If anything tries to delete this->handler_ (e.g. what the destructor would do normally on reasonable classes with pointer members) you're double-freeing memory (undefined behavior), or if they try to use it after the unique_ptr cleans it, you're accessing freed memory (also undefined).

Basically, option #1 is almost certainly wrong, and even when it's not wrong, it's the worst sort of code smell (a pointer you allocate and store as an instance attribute, and don't delete? shudders). You want option #2, or more precisely, a simpler version of it:

auto res1 = root()->grpcStreamHandler(std::move(handler_));

that avoids explicitly constructing the new unique_ptr passed to the method when it would be move-constructed anyway. And really, you probably don't want this->handler_ at all if all you're doing is immediately moving from it, just do:

auto res1 = root()->grpcStreamHandler(std::unique_ptr<Y>(new X(this)));

and construct/pass it as a single action (which may be able to elide the move).



Related Topics



Leave a reply



Submit