std::vector::push_back 一个不可复制的对象给出编译器错误
我在 g++ (GCC) 4.7.2
上遇到编译错误,但在尝试 std::vector::push_back
时却没有代码> 不可复制(私有复制构造函数)但可移动的对象.对我来说,我的示例看起来与 SO 和其他地方的许多其他示例相同.错误消息使它看起来像结构不是直接可构造"的问题 - 我不知道这意味着什么,所以我非常不确定为什么对象需要直接构造"才能被推回.
I get compilation errors on g++ (GCC) 4.7.2
but not on MSVC-2012
when trying to std::vector::push_back
a non-copyable (private copy constructor) but moveable object. To me my example looks identical to many other examples on SO and elsewhere. The error message makes it looks like a problem with the struct not being 'direct constructible' - I don't know what this means so am doubly unsure about why an object needs to be 'direct constructible' to be pushed back.
#include <vector>
#include <memory>
struct MyStruct
{
MyStruct(std::unique_ptr<int> p);
MyStruct(MyStruct&& other);
MyStruct& operator=(MyStruct&& other);
std::unique_ptr<int> mP;
private:
// Non-copyable
MyStruct(const MyStruct&);
MyStruct& operator=(const MyStruct& other);
};
int main()
{
MyStruct s(std::unique_ptr<int>(new int(5)));
std::vector<MyStruct> v;
auto other = std::move(s); // Test it is moveable
v.push_back(std::move(other)); // Fails to compile
return 0;
}
给出错误
/usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/type_traits: In instantiation of ‘struct std::__is_direct_constructible_impl<MyStruct, const MyStruct&>’:
... snip ...
/usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/bits/stl_vector.h:900:9: required from ‘void std::vector<_Tp, _Alloc>::push_back(std::vector<_Tp, _Alloc>::value_type&&) [with _Tp = MyStruct; _Alloc = std::allocator<MyStruct>; std::vector<_Tp, _Alloc>::value_type = MyStruct]’
main.cpp:27:33: required from here
main.cpp:16:5: error: ‘MyStruct::MyStruct(const MyStruct&)’ is private
各种答案的简单解决方法:
- 使用
MyStruct(const MyStruct&) = delete;
而不是private ctor
hack - 继承
boost::noncopyable
(或其他具有私有 ctor 的类)
- Use
MyStruct(const MyStruct&) = delete;
instead ofprivate ctor
hack - Inherit
boost::noncopyable
(or another class with private ctor)
推荐答案
失败是由于 G++ 4.7 的限制,它没有实现 DR 1170,在 C++11 标准化过程的后期进行了更改,表示访问检查应该作为模板参数推导的一部分进行.
The failure is due to a limitation of G++ 4.7, which doesn't implement DR 1170, which was changed very late in the C++11 standardisation process to say that access checking should be done as part of template argument deduction.
根本原因是 libstdc++ 的 vector
将移动元素,如果移动操作保证不抛出(即声明为 noexcept
或 throw()
),否则如果类型是可复制的,则元素将被复制,否则如果类型不可复制但确实有可能抛出的移动操作,则它将被移动(如果抛出异常,则结果为 undefined 未指定.)这是通过检查 is_nothrow_move_constructible
和 is_copy_constructible
类型特征来实现的.在您的情况下,该类型不是可构造的,因此检查了 is_copy_constructible
特征.您的类型有一个复制构造函数,但它不可访问,因此 is_copy_constructible
特征会在 G++ 4.7 中产生编译器错误,因为在模板参数推导期间未进行访问检查.
The underlying cause is that libstdc++'s vector
will move elements if the move operation is guaranteed not to throw (i.e. it's declared noexcept
or throw()
), otherwise if the type is copyable the elements will be copied, otherwise if the type is not copyable but does have a possibly-throwing move operation then it will be moved (and if an exception is thrown the results are undefined unspecified.) This is implemented with checks to the is_nothrow_move_constructible
and is_copy_constructible
type traits. In your case, the type is not nothrow move constructible, so the is_copy_constructible
trait is checked. Your type has a copy constructor but it's not accessible, so the is_copy_constructible
trait produces a compiler error with G++ 4.7 because access checking is not done during template argument deduction.
如果你让你的移动构造函数和移动赋值运算符 noexcept
那么类型将被移动并且不需要是可复制的,所以失败的 is_copy_constructible
特征是没用过,代码编译OK.
If you make your move constructor and move assignment operator noexcept
then the type will be moved and doesn't need to be copyable, so the is_copy_constructible
trait that fails is not used, and the code compiles OK.
或者,(如评论中所述)如果您删除了复制构造函数,则 is_copy_constructible
特征会得到正确的结果.
Alternatively, (as also stated in the comments) if you make the copy constructor deleted then the is_copy_constructible
trait gets the right result.
另一种选择是使用类似 boost::noncopyable
的东西,它会隐式删除复制构造函数,因此 is_copy_constructible
特征可以正常工作(并且也适用于 MSVC 等较旧的编译器不支持正确删除的功能).我不知道您所说的无法找到错误是什么意思,MSVC 没有向您显示编译器错误的完整上下文吗?
Another alternative is to use something like boost::noncopyable
which implicitly makes the copy constructor deleted so the is_copy_constructible
trait works properly (and also works with older compilers like MSVC that don't support deleted functions properly). I don't know what you mean about making it impossible to find the error, does MSVC not show you the full context of a compiler error?
结论:在适当的地方使用 unique_ptr 但不要让类显式可移动
Conclusion: use unique_ptr where appropriate but don't make classes explicitly movable
我不同意这个结论,太极端了.相反,尽可能让你的类不能移动.此外,在可能的情况下,使用已删除的函数来使类型不可复制,而不是私有+未实现的函数,可能使用宏来移植到旧编译器,例如
I disagree with this conclusion, it is too extreme. Instead make your classes nothrow movable whenever possible. Also, when possible, use deleted functions to make a type non-copyable instead of private+unimplemented functions, maybe using a macro for portability to older compilers e.g.
#if __cplusplus >= 201103L
#define NONCOPYABLE(TYPE)
TYPE(const TYPE&) = delete; TYPE& operator=(const TYPE&) = delete
#else
// must be used in private access region
#define NONCOPYABLE(TYPE)
TYPE(const TYPE&); TYPE& operator=(const TYPE&)
#endif
struct MyStruct
{
...
private:
NONCOPYABLE(MyStruct);
};
相关文章