Insert into vector having objects without copy con

2020-02-08 06:41发布

问题:

I have a class whose copy constructors are explicitly deleted (because A uses pointers internally and I don't want to fall into shallow copy pitfalls):

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

    A(const B& b, const C& c);
}

Now I have a vector of type vector<A> aVector; and I want to insert elements into it - so I use emplace_back:

aVector.emplace_back(b, c);

However, this fails to compile using gcc and I get the error -

third-party/gcc-4.7.1-glibc-2.14.1/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1/bits/stl_construct.h: In instantiation of 'void std::_Construct(_T1*, _Args&& ...)
third-party/gcc-4.7.1-glibc-2.14.1/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1/bits/stl_uninitialized.h:77:3:   required from 'static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator)  
third-party/gcc-4.7.1-glibc-2.14.1/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1/bits/stl_uninitialized.h:119:41:   required from '_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) 
third-party/gcc-4.7.1-glibc-2.14.1/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1/bits/stl_uninitialized.h:260:63:   required from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) 
third-party/gcc-4.7.1-glibc-2.14.1/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1/bits/stl_uninitialized.h:283:67:   required from '_ForwardIterator std::__uninitialized_move_if_noexcept_a(_InputIterator, _InputIterator, _ForwardIterator, _Allocator&)
third-party/gcc-4.7.1-glibc-2.14.1/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1/bits/vector.tcc:410:6:   required from 'void std::vector<_Tp, _Alloc>::_M_emplace_back_aux(_Args&& ...) 
third-party/gcc-4.7.1-glibc-2.14.1/libgcc/libgcc-4.7.1/afc21dc/include/c++/4.7.1/bits/vector.tcc:102:4:   required from 'void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...)

What is the reason for this error and how can it be fixed without removing the deletion of the copy constructors? Do I need a move constructor - does it need to be explicitly defined?

回答1:

You should add move constructor - because std::vector::emplace_back may do relocation which requires copy/move constructor. Or just use std::deque.

LIVE DEMO

#include <vector>
#include <deque>
using namespace std;

struct NoCopyNoMove
{
    NoCopyNoMove(const NoCopyNoMove&) = delete;
    NoCopyNoMove& operator=(const NoCopyNoMove&) = delete;
    NoCopyNoMove(NoCopyNoMove&&) = delete;
    NoCopyNoMove& operator=(NoCopyNoMove&&) = delete;

    NoCopyNoMove(int){};
};

struct OnlyMove
{
    OnlyMove(const OnlyMove&) = delete;
    OnlyMove& operator=(const OnlyMove&) = delete;
    OnlyMove(OnlyMove&&) noexcept {}
    OnlyMove& operator=(OnlyMove&&) noexcept {}

    OnlyMove(int){};
};

int main()
{
    deque<NoCopyNoMove> x;
    x.emplace_back(1);

    vector<OnlyMove> y;
    y.emplace_back(1);
}

§ 23.2.3 Table 101 — Optional sequence container operations

a.emplace_back(args) [...]

Requires: T shall be EmplaceConstructible into X from args. For vector, T shall also be MoveInsertable into X.



回答2:

The error is not the fault of emplace_back. To put an object in a vector it must be movable or copyable. If you actually run the code with copy constructor implemented you will notice it is never called. This is an entry on cppreference.com

What I would do to fix this is implement the move constructor, that makes it compile and I can't see any really drawback to having a move constructor. And as with the cctor the move constructor will not be called in your current code.



回答3:

I ran into this problem with an external library's class. I was getting,

"Error C2280 ClassName::ClassName(const ClassName &)': attempting to reference a deleted function"

I'm guessing that the class I was using had deleted its copy constructor. I couldn't add it to any std containers I knew of for my custom derived-class objects, which wrapped their object with some helpers of mine to help with initialization/error checks.

I worked around this blocker with (risky) pointers.

Basically, I transitioned to this:

std::vector<ClassName*> names;
ClassName name("arg");
ClassName name_ptr = &name;
names.push_back(name_ptr);

from this, originally:

std::vector<ClassName> names;
ClassName name("arg");
names.push_back(name);

Interesting to say, this was the first time coding with C++ that I've actually needed to use pointers for non-pointer-specific usage requirements due to no known alternative. That makes me worry that I may missed something fundamental within my own code.

Maybe there's a better way to do this, but it's not on this question's list of answers yet...

edit for Caveat:

I should have mentioned this before, thanks aschepler; if you do this and the container you're using outlives the object, "bang, you're dead."



回答4:

Just want to add to @kayleeFrye_onDeck's answer. I have a near identical situation to theirs, and the exact syntax that works for me (based on the feedback in the comments) is as follows:

vector< std::unique_ptr<ClassName> > names; // Declare vector of unique_ptrs of the class instance

std::unique_ptr<ClassName> name_ptr = std::make_unique<ClassName>();
names.push_back(std::move(name_ptr)); // Need to use std::move()

// Now you can access names objects without error:
names[0]->classMethod();