C++ 向量 emplace_back 调用复制构造函数
C++ vector emplace_back calls copy constructor
这是一个演示 class。我不希望我的 class 被复制,所以我删除了复制构造函数。我希望 vector.emplace_back 使用此构造函数 'MyClass(Type type)'。但是这些代码不会编译。为什么?
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);
vector
需要复制构造函数,以便它可以在需要增加存储空间时复制元素。
您可以阅读 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.
一些日志记录可以帮助您了解正在发生的事情
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);
}
输出为
create 1
create 2
create 3
copy 1
copy 2
因此您可以 emplace_back
确实使用所需的构造函数来创建元素并在需要增加存储时调用复制构造函数。您可以预先调用具有足够容量的 reserve
以避免调用复制构造函数。
如果出于某种原因你真的不希望它是可复制构造的,你可以使用 std::list
而不是 std::vector
因为 list
是作为链表实现的,它不会'需要移动元素。
只是问题的精确度。如果我们不希望在容器重新分配时使用对象复制构造,那么使用移动构造函数确实是可能的,但前提是它具有 noexcept 规范。
如果构造函数可能抛出异常,容器将拒绝移动构造元素,因为这可能导致容器处于无法清理的不良状态。这就是为什么当我们确定它永远不会抛出任何异常时,将移动构造函数指定为 noexcept 通常是一个好习惯的原因。
根据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 特别需要移动构造函数或复制构造函数。
所以,如果您不希望您的 class 被复制,您应该添加一个显式移动构造函数。您可以使用 = default
来使用编译器提供的仅移动所有字段的默认实现。
完整示例
#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);
}
备注
请注意,您可能会感到非常困惑
error: use of deleted function ‘MyClass::MyClass(const MyClass&)
使用 C++17 时使用
auto x = list.emplace_back(MyClass::e1);
而不是
auto& x = list.emplace_back(MyClass::e1);
即使使用移动构造函数。
这是一个演示 class。我不希望我的 class 被复制,所以我删除了复制构造函数。我希望 vector.emplace_back 使用此构造函数 'MyClass(Type type)'。但是这些代码不会编译。为什么?
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);
vector
需要复制构造函数,以便它可以在需要增加存储空间时复制元素。
您可以阅读 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.
一些日志记录可以帮助您了解正在发生的事情
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);
}
输出为
create 1
create 2
create 3
copy 1
copy 2
因此您可以 emplace_back
确实使用所需的构造函数来创建元素并在需要增加存储时调用复制构造函数。您可以预先调用具有足够容量的 reserve
以避免调用复制构造函数。
如果出于某种原因你真的不希望它是可复制构造的,你可以使用 std::list
而不是 std::vector
因为 list
是作为链表实现的,它不会'需要移动元素。
只是问题的精确度。如果我们不希望在容器重新分配时使用对象复制构造,那么使用移动构造函数确实是可能的,但前提是它具有 noexcept 规范。
如果构造函数可能抛出异常,容器将拒绝移动构造元素,因为这可能导致容器处于无法清理的不良状态。这就是为什么当我们确定它永远不会抛出任何异常时,将移动构造函数指定为 noexcept 通常是一个好习惯的原因。
根据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 特别需要移动构造函数或复制构造函数。
所以,如果您不希望您的 class 被复制,您应该添加一个显式移动构造函数。您可以使用 = default
来使用编译器提供的仅移动所有字段的默认实现。
完整示例
#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);
}
备注
请注意,您可能会感到非常困惑
error: use of deleted function ‘MyClass::MyClass(const MyClass&)
使用 C++17 时使用
auto x = list.emplace_back(MyClass::e1);
而不是
auto& x = list.emplace_back(MyClass::e1);
即使使用移动构造函数。