Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nesting std::containers of movable objects

I have a class NoCopy that is movable, but not copiable.

I need to make a vector of 3 queues of NoCopy. I can create an empty one, but there is no way to add any element.

I can make a std::vector<NoCopy> or std::queue<NoCopy> and populate them. But not for std::vector<std::queue<NoCopy>>.

MWE:

#include <iostream>
#include <vector>
#include <queue>

class NoCopy{
public:
    NoCopy() = default;
    NoCopy& operator = (const NoCopy&) = delete;
    NoCopy(const NoCopy&) = delete;

    NoCopy(NoCopy&&) = default;
    NoCopy& operator = (NoCopy&&) = default;

};
using QNC = std::queue<NoCopy>;

int main(void) {
    QNC q;
    q.push(std::move(NoCopy()));

    std::vector<NoCopy> ncvec;
    ncvec.emplace_back();

    std::cout << "Queue size " << q.size() << ", vector size: " << ncvec.size() << std::endl;

    std::vector<QNC> qvec;
    //????

    return 0;
}

Any ideas?

like image 663
Ondřej Navrátil Avatar asked Apr 17 '20 10:04

Ondřej Navrátil


1 Answers

By default,std::queue is based on std::deque, which is not guaranteed to be nothrow-movable. Neither is the other suitable standard container, std::list; these rules allow implementations that always allocate at least one node/block. std::vector uses copies to reallocate when moving might throw (so as to guarantee exception safety) unless the type is not copyable at all, and those two containers also do not propagate non-copyability from their element type but just fail if you try. The last could be said to be a defect in the standard, since expectations for such propagation keep going up, but fixing it is incompatible with support for incomplete types:

struct Node {
  std::vector<Node> children;
  // …
};

Note that both libstdc++ and libc++ do make those two containers nothrow-movable (as of version 9 in each case), which is a permitted extension, but MSVC’s STL does not.

You can still use std::vector<QNC> v(3);; the constructor “knows” that reallocation is never necessary. Or you can provide a wrapper (e.g., a class derived from std::queue) that is non-copyable or is nothrow-movable; the former will waive the exception safety of std::vector, while the latter will call std::terminate if moving the underlying container does throw.

like image 91
Davis Herring Avatar answered Oct 20 '22 01:10

Davis Herring