简体   繁体   English

C++ 向量 emplace_back 调用复制构造函数

[英]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)'.我希望 vector.emplace_back 使用这个构造函数 '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);

The copy constructor is required by vector so that it can copy the element when it need to grow its storage. vector需要复制构造函数,以便它可以在需要增加其存储空间时复制元素。

You can read the document for vector您可以阅读矢量文档

T must meet the requirements of CopyAssignable and CopyConstructible . T 必须满足 CopyAssignable 和CopyConstructible的要求。 (until C++11) (直到 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.一般要求元素类型是完整类型,满足Erasable的要求,但是很多成员函数的要求比较严格。 (since C++11) (until C++17) (C++11 起) (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.一般要求元素类型满足Erasable的要求,但是很多成员函数的要求比较严格。 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.因此,您可以emplace_back确实使用所需的构造函数来创建元素并在需要增加存储空间时调用复制构造函数。 You can call reserve with enough capacity upfront to avoid the need to call copy constructor.您可以预先调用具有足够容量的reserve以避免调用复制构造函数。


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.如果由于某种原因你真的不希望它是可复制构造的,你可以使用std::list而不是std::vector因为list是作为链表实现的,它不需要移动元素。

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

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.如果我们不希望在发生容器重新分配时使用对象复制构造,则确实可以使用移动构造函数,但前提是它具有 noexcept 规范。

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.这就是为什么当我们确定它永远不会抛出任何异常时,将移动构造函数指定为 noexcept 通常是一个好习惯的原因。

According tohttps://en.cppreference.com/w/cpp/container/vector/emplace_back , the value_type of a std::vector<T> needs to be MoveInsertable and EmplaceConstructible .根据https://en.cppreference.com/w/cpp/container/vector/emplace_backstd::vector<T>value_type需要是MoveInsertableEmplaceConstructible MoveInsertable in particular requires a move constructor or a copy constructor. MoveInsertable特别需要移动构造函数或复制构造函数。

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.您可以使用= default来使用编译器提供的仅移动所有字段的默认实现。

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使用 C++17 时

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

instead of而不是

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

even with the move constructor.即使使用移动构造函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM