wqyfavor
wqyfavor

Reputation: 539

C++ vector emplace_back calls copy constructor

This is a demo class. I do not want my class to be copied, so I delete the copy constructor. I want vector.emplace_back to use this constructor 'MyClass(Type type)'. But these codes won't compile. Why?

class MyClass
{
public:
    typedef enum
    {
        e1,
        e2
    } Type;
private:
    Type _type;
    MyClass(const MyClass& other) = delete; // no copy
public:
    MyClass(): _type(e1) {};
    MyClass(Type type): _type(type) { /* the constructor I wanted. */ };
};

std::vector<MyClass> list;
list.emplace_back(MyClass::e1);
list.emplace_back(MyClass::e2);

Upvotes: 23

Views: 20024

Answers (3)

Xiphoseer
Xiphoseer

Reputation: 61

According to https://en.cppreference.com/w/cpp/container/vector/emplace_back, the value_type of a std::vector<T> needs to be MoveInsertable and EmplaceConstructible. MoveInsertable in particular requires a move constructor or a copy constructor.

So, if you don't want your class to be copied, you should add an explicit move constructor. You can use = default to use the compiler-provided default implementation that just moves all fields.

Full example

#include <vector>

class MyClass
{
public:
    typedef enum
    {
        e1,
        e2
    } Type;
private:
    Type _type;
    MyClass(const MyClass& other) = delete; // no copy
public:
    MyClass(): _type(e1) {};
    MyClass(MyClass&&) noexcept = default; // < the new move constructor
    MyClass(Type type): _type(type) { /* the constructor I wanted. */ };
};

int main() {
    std::vector<MyClass> list;
    list.emplace_back(MyClass::e1);
    list.emplace_back(MyClass::e2);
}

Note

Note that you can get a very confusing

error: use of deleted function ‘MyClass::MyClass(const MyClass&)

with C++17 when you use

auto x = list.emplace_back(MyClass::e1);

instead of

auto& x = list.emplace_back(MyClass::e1);

even with the move constructor.

Upvotes: 4

hlsystran
hlsystran

Reputation: 83

Just a precision for the issue. If we don't want objects copy construction to be used when a reallocation of the container occurs, it is indeed possible with a move constructor but only if it has the noexcept specification.

Containers refuse to move construct elements if the constructor might throw an exception because it could lead to a container in a bad state that cannot be cleaned. That's the reason why it is generally a good practice to specify a move constructor as noexcept when we are sure it will never throw any exceptions.

Upvotes: 7

Bryan Chen
Bryan Chen

Reputation: 46578

The copy constructor is required by vector so that it can copy the element when it need to grow its storage.

You can read the document for vector

T must meet the requirements of CopyAssignable and CopyConstructible. (until C++11)

The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type is a complete type and meets the requirements of Erasable, but many member functions impose stricter requirements. (since C++11) (until C++17)

The requirements that are imposed on the elements depend on the actual operations performed on the container. Generally, it is required that element type meets the requirements of Erasable, but many member functions impose stricter requirements. This container (but not its members) can be instantiated with an incomplete element type if the allocator satisfies the allocator completeness requirements.

Some logging can help you understand what's going on

For this code

class MyClass
{
public:
    typedef enum
    {
        e1 = 1,
        e2 = 2,
        e3 = 3,
    } Type;
private:
    Type _type;
public:
    MyClass(Type type): _type(type) { std::cout << "create " << type << "\n"; };
    MyClass(const MyClass& other) { std::cout << "copy " << other._type << "\n"; }
};

int main() {
    std::vector<MyClass> list;
    list.reserve(2);
    list.emplace_back(MyClass::e1);
    list.emplace_back(MyClass::e2);
    list.emplace_back(MyClass::e3);
}

The output is

create 1
create 2
create 3
copy 1
copy 2

So you can emplace_back does use the desired constructor to create the element and call copy constructor when it need to grow the storage. You can call reserve with enough capacity upfront to avoid the need to call copy constructor.


If for some reason you really don't want it to be copy constructible, you can use std::list instead of std::vector as list is implemented as linked list, it doesn't need to move the elements.

http://coliru.stacked-crooked.com/a/16f93cfc6b2fc73c

Upvotes: 23

Related Questions