Question

I'm trying to do strange things again.

Okay, here's the general idea. I want a std::list (and vector and so on) that actually own the objects they contain. I want to move the values into it, and access them by reference.

Example using a list of unique_ptr:

using namespace std;
list<unique_ptr<T>> items;     // T is whatever type
items.push_back(make_unique(...));
items.push_back(make_unique(...));

for ( unique_ptr<T> item : items )
    item.dosomething();

With me so far? Good. Now, let's do it with stack semantics and rvalue references. We can't just use a list<T&&> for obvious reasons, so we'd have to make a new class:

using namespace std;
owninglist<T> items;
items.push_back(T());
items.push_back(T());

for ( T& item : items )
    item.dosomething();

Of course, I might want an owningstack or owningvector as well, so ideally we want it to be templated:

owning<std::list<T>> items;

The owning<U<T>> class should inherit whatever push_back() and pop_front() etc functions the underlying collection has. Presumably to achieve that, I'd need to code a generic base class, and derive explicit specialisations for the collections that have unusual functions:

template<typename T> owning<std::queue<T>> : owningbase<T> {
    void push_front() { ... }
}

I'm getting stuck on the iterators. The begin() and end() functions should return an iterator that works the same as the underlying collection's iterator would, except with an operator*() that returns the item by lvalue reference instead of by value.

We'd need some way to transfer ownership of an item out of the list again. Perhaps the iterator could have an operator~ that returns the item as an rvalue, deletes the item from the list, and invalidates the iterator?

Of course, all this is assuming the underlying std::list (or whatever) can be convinced to take an rvalue. If push_back() copies the value in as an lvalue, then none of this is going to work. Would I be better off coding a container from scratch? If I did, is there some way to put the majority of the code for list, queue, stack and vector into a single base class, to save rewriting pretty much the same class four times over?

Perhaps I could introduce an intermediate class, some kind of wrapper? So owned<list<T>> could inherit from list<refwrapper<T>> or something? I know boost has a reference_wrapper, but I'm not sure it fits this scenario.

Was it helpful?

Solution

If you want to avoid copy elements around you can use std::move.

So if you have a std::list you can populate it with values by moving them in:

SomeBigObject sbo;

std::list<SomeBigObject> list;
list.push_back(SomeBigObject()); // SomeBigObject() is a rvalue and so it is moved
list.push_back(std::move(sbo));  // sbo may not be a rvalue so you have to move it

// For construction you can also use std::list::emplace
list.emplace(list.end());        // construct the value directly at the end of the list

For accessing them you can simply use the ranged based loop:

for(auto& i :list)
    ...

If you want to move them out of the container you can also use std::move. The object is moved out of the container but the remains will still be in the container, so you have to erase them:

for(auto it = list.begin; it != lsit.end();)
{
    // the value of *it is moved into obj;
    // an empty value of "SomeBigObject" will remain so erase it from the list
    SomeBigObject obj = std::move(*it);

    it = list.erase(it);

    // do something with "obj"
    ...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top