Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to correctly move ownership from raw pointer to std::unique_ptr?

My approach is:

class SomeClass
{
    std::vector<std::unique_ptr<MyObject>> myObjects;
public:
    void takeOwnership(MyObject *nowItsReallyMyObject)
    {
        myObjects.emplace_back(std::move(nowItsReallyMyObject));
    }
};

Am I doing everything correctly or are there any better solutions?

like image 684
Oliort UA Avatar asked May 08 '17 21:05

Oliort UA


People also ask

How do I transfer ownership of a unique pointer?

In C++11 we can transfer the ownership of an object to another unique_ptr using std::move() . After the ownership transfer, the smart pointer that ceded the ownership becomes null and get() returns nullptr.

Can unique_ptr be moved?

A unique_ptr can only be moved. This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it. We recommend that you restrict an object to one owner, because multiple ownership adds complexity to the program logic.

How is unique_ptr implemented in C++?

unique_ptr allows only one owner of the underlying pointer while shared_ptr is a reference-counted smart pointer. In this implementation, the developer doesn't need to explicitly delete the allocated memory towards the end of the function.

What is smart pointer when should we use it asked me to implement unique_ptr of my own?

std::unique_ptr s are useful when you want to tie the lifetime of the object to a particular block of code, or if you embedded it as member data inside another object, the lifetime of that other object. The object exists until the containing block of code is exited, or until the containing object is itself destroyed.


2 Answers

The move is redundant.

Myself, I'd do this:

void takeOwnership(std::unique_ptr<MyObject> nowItsReallyMyObject)
{
    myObjects.emplace_back(std::move(nowItsReallyMyObject));
}

because I would want to move the unique_ptr ownership semantics as far "out" as possible.

I might write this utility function:

template<class T>
std::unique_ptr<T> wrap_in_unique( T* t ) {
  return std::unique_ptr<T>(t);
}

so callers can:

foo.takeOwnership(wrap_in_unique(some_ptr));

but even better, then can push the borders of unique_ptr semantics out as far as they reasonably can.

I might even do:

template<class T>
std::unique_ptr<T> wrap_in_unique( T*&& t ) {
  auto* tmp = t;
  t = 0;
  return std::unique_ptr<T>(tmp);
}
template<class T>
std::unique_ptr<T> wrap_in_unique( std::unique_ptr<T> t ) {
  return std::move(t);
}

which lets callers transition their T* into unique_ptrs easier. All of their T*->unique_ptr<T> is now wrapped in a std::move, and zeros the source pointer.

So if they had

struct I_am_legacy {
  T* I_own_this = 0;
  void GiveMyStuffTo( SomeClass& sc ) {
    sc.takeOwnership( wrap_in_unique(std::move(I_own_this)) );
  }
};

the code can be transformed into:

struct I_am_legacy {
  std::unique_ptr<T> I_own_this;
  void GiveMyStuffTo( SomeClass& sc ) {
    sc.takeOwnership( wrap_in_unique(std::move(I_own_this)) );
  }
};

and it still compiles and works the same. (Other interaction with I_own_this may have to change, but part of it will already be unique_ptr compatible).

like image 150
Yakk - Adam Nevraumont Avatar answered Sep 21 '22 02:09

Yakk - Adam Nevraumont


You should accept the unique_ptr from the get-go:

class SomeClass
{
    std::vector<std::unique_ptr<MyObject>> myObjects;
public:
    // tells the world you 0wNz this object
    void takeOwnership(std::unique_ptr<MyObject> myObject)
    {
        myObjects.push_back(std::move(myObject));
    }
};

This way you make it clear you take ownership and you also help other programmers to avoid using raw pointers.

Further reading: CppCoreGuidelines R.32

like image 24
Galik Avatar answered Sep 20 '22 02:09

Galik