Copyable and movable
对于resource wrapper需要明确copyable、movable
我的经历
1、resource handler是来源自第三方的network library,resource wrapper使用RAII,但是没有指定它的copyable 和 movable,而是依赖于implicit defined implementation;
显然我的这种做法是没有遵循The rule of three/five/zero的,因为我custom了destructor,但是没有对它的copyable、movable进行指定
2、我推测compiler会执行copy-elision,即不会发生copy,然而在复杂的C++规则面前,programmer可能无法准确的预测compiler的行为,结果发生了copy,而implicit defined copy是shallow copy,并且在定义了destructor的情况下,compiler是不会生成movable的 ,这就导致了"implicit-define-copy-constructor-shallow-copy-double-free"错误。
明确copyable、movable
对于resource wrapper,需要明确指定它的copyable、movable,下面是一些example:
Copyable and movable: std::string
Non-copyable but movable: 很多resource wrapper都是这种类型的、std::unique_ptr
Copyable but non-movable: ?
Neither copyable nor movable: std::mutex
作为programmer,需要考虑class是否copyable、movable,并进行显式地指定(比如使用delete
),不要推测、过分依赖于compiler的行为。不应该死板的套用rule of three/five/zero,而是根据具体的情况进行准确的阐明。
本质是对resource ownership的阐明
NOTE:
1、在resource ownership章节,对这问题已经进行了探讨。
其实对copyable、movable的思考,本质上是对ownership的思考,"implicit defined copy constructor shallow copy double free"问题,其实就是典型对ownership描述不清而导致的问题,它到底是unique ownership还是shared ownership?显然raw pointer无法描述清楚,shallow copy对应的更多是shared ownership,而raw pointer无法表达shared ownership,它会导致double free、dangling。
因此在使用resource handle的时候,一定要同时阐明清楚它的ownership,最好是使用unique_ptr
、shared_ptr
,它们已经明确的阐明清楚了ownership。
What is "Copyable concept"、"Movable concept"?
本文首先说明清楚"Copyable concept"、"Movable concept",简而言之:
一、本文中的copyable对应的是:
copy constructor、copy assignment operator
二、本文中的movable对应的是:
move constructor、move assignment operator
Copyable concept
cppreference C++ named requirements: CopyConstructible
Specifies that an instance of the type can be copy-constructed from an lvalue expression.
Expression | Post-conditions |
---|---|
T u = v; | The value of u is equivalent to the value of v .The value of v is unchanged |
T(v) | The value of T(v) is equivalent to the value of v .The value of v is unchanged. |
Notes
NOTE:
1、这一段内容其实所讨论的是"address of idiom",将它放到了
Address-of-idiom
章节了
cppreference C++ named requirements: CopyAssignable
Specifies that an instance of the type can be copy-assigned from an lvalue expression.
Expression | Return type | Return value | Post-conditions |
---|---|---|---|
t = v | T& |
t |
The value of t is equivalent to the value of v .The value of v is unchanged. |
Movable concept
cppreference C++ named requirements: MoveConstructible (since C++11)
Expression | Post-conditions |
---|---|
T u = rv; | The value of u is equivalent to the value of rv before the initialization.The new value of rv is unspecified. |
T(rv) | The value of T(rv) is equivalent to the value of rv before the initialization.The new value of rv is unspecified. |
Notes
A class does not have to implement a move constructor to satisfy this type requirement: a copy constructor that takes a const T&
argument can bind rvalue expressions.
NOTE:
1、虽然可以"bind rvalue expression",但是无法对它进行修改。也就无法发挥move semantic
If a MoveConstructible class implements a move constructor, it may also implement move semantics to take advantage of the fact that the value of rv
after construction is unspecified.
cppreference C++ named requirements: MoveAssignable (since C++11)
Expression | Return type | Return value | Post-conditions |
---|---|---|---|
t = rv | T& |
t |
If t and rv do not refer to the same object , the value of t is equivalent to the value of rv before the assignment.The new value of rv is unspecified |
Notes
The type does not have to implement move assignment operator in order to satisfy this type requirement: a copy assignment operator that takes its parameter by value or as a const Type&
, will bind to rvalue argument.
If a MoveAssignable class implements a move assignment operator, it may also implement move semantics to take advantage of the fact that the value of rv
after assignment is unspecified.
Google C++ Style Guide Copyable and Movable Types
boost Movable but Non-Copyable Types
Non-copyable
一、singleton
二、polymorphic class
三、guard
1、cppreference std::lock_guard
The lock_guard
class is non-copyable.
Movable but non-copyable
Resource wrapper
一、一般,一个resource wrapper,它own(拥有) resource(ownership),它的lifetime会和resource进行绑定,一般采用RAII、resource handle的方式来进行实现,这就是之前所总结的Object-based resource management。
二、对于这样的resource wrapper,它们一般都是non-copyable but movable的,因为,一旦允许copy,那么久可能导致多个object同时share同一个resource,在这种情况下,就需要考虑shared ownership了:
1、raw pointer无法实现shared ownership,如果其中的一个object end,那么在它的destructor中就会将resource给release,这就可能导致其他的依然存活的object使用这个resource的时候,出现错误,这会导致:
dangling pointer、double free
2、std::shared_ptr
可以实现shared ownership
Example
std::thread
No two std::thread
objects may represent the same thread of execution; std::thread
is not CopyConstructible or CopyAssignable, although it is MoveConstructible and MoveAssignable.
ariya C++ Class and Preventing Object Copy
A class that represents a wrapper stream of a file should not have its instance copied around. It will cause a confusion in the handling of the actual I/O system.
In a similar spirit, if an instance holds a unique private object, copying the pointer does not make sense.
redis-plus-plus class Redis
在阅读redis client redis-plus-plus 的时候,其中介绍到:
Redis
class is movable but NOT copyable.
std::unique_ptr
The class satisfies the requirements of MoveConstructible and MoveAssignable, but of neither CopyConstructible nor CopyAssignable.
Neither copyable nor movable
Example
std::mutex
std::mutex
is neither copyable nor movable.
stackoverflow Why is std::mutex neither copyable nor movable? [duplicate]
stackoverflow Why is there no need to mark the move constructor of a type with a deleted copy constructor as deleted?
stackoverflow Move constructor for std::mutex
std::atomic
cppreference2015 std::atomic
std::atomic
is neither copyable nor movable.