Question

Say I have a std::list of class T's:

std::list<T> l;

When passing it into functions, I would use a reference:

someFunction( std::list<T> &l )

What is the best way to pass around (the elements) of a std::list of unique_ptrs?

std::list< std::unique_ptr<T> >  l;

Like this:

someFunction( std::unique_ptr<T> ptr )

Or this:

someFunction( T* ptr )

Or this:

someFunction( T &ref )

And what how would I call it using the std::list's back() function for example? These are IMHO all "kind of" equivalent, but I'm sure I'm missing something here.

Thanks

Was it helpful?

Solution

In order of best to worse:

  1. someFunction(const T&);
  2. someFunction(T&);
  3. someFunction(const std::unique_ptr<T>&);
  4. someFunction(std::unique_ptr<T>&);

The first one is the best because it does not modify the object it and it will work with the object no matter how you have allocated it (for example, you could switch to shared_ptr with no problems).

Number two will also work regardless of what smart pointer you are using; however, it assumes that you can modify the object, and whenever you can make something const, you should.

Numbers 3 and 4 both allow the object being pointed-to to be mutated; however, #3 does not allow the smart pointer to be modified, while number 4 does. Both have the disadvantage that they force the use of unique_ptr, whereas the two above it would work regardless of smart pointer class.

Passing a unique_ptr by value, as you have in some of the other examples is not an option; a unique_ptr is supposed to be unique. If you are copying it, consider using shared_ptr.

For the first two, if you invoked it on the result of back(), it would look like:

someFunction(*(lst.back()));  // dereference lst.back() before passing it in.

For the latter two, if you invoked it on the resut of back(), it would look like:

someFunction(lst.back()); // pass the smart pointer, not the object to
                          // which the smart pointer currently points.

OTHER TIPS

Do not pass unique_ptr by value, first of all it won't compile without a std::move and if you do use std::move it will empty the value you have stored in your list and you won't be able to access it any more.

This is because unique_ptr is not copyable, it doesn't have a copy constructor of type unique_ptr::unique_ptr(const unique_ptr<T>& other) instead it only has a move constructor (unique_ptr::unique_ptr(unique_ptr<T>&& source)).

unique_ptr and also classes / instances containing unique_ptr can be used in std::list (and other containers), provided that they have move constructor class_name(class_name &&) defined (which unique_ptr, of course, has).

When you pass around those elements, you're always moving (and not copying) them, so you always use std::move() on lvalues, like in
my_list.push_back(std::move(my_element));
this makes visible that you're passing (= moving) the element into the list, and that my_element is "empty" (like empty unique_ptr) after that operation.

Example:

typedef std::unique_ptr<uint8_t[]> data_ptr;

class data_holder
{
private:
    int something_about_data;
    data_ptr data;
public:
    data_holder(data_ptr && _data)
        : data(std::move(_data))
    {}

    // hey compiler, please generate a default move constructor for me
    // despite of present destructor
    data_holder(data_holder &&) = default;

    ~data_holder()
    {
        // ...
    }

    bool is_empty() const { return ! bool(data); }
}

// ...
{
    // ...
    data_holder the_element(data_ptr(new uint8_t[DATA_SIZE]));

    // move the_element into the_list
    the_list.push_back(std::move(the_element));
    if (the_element.is_empty())
        std::cerr << "data_holder 'the_element' is now empty!" << std::endl;
    // ...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top