

Modern C++ Basics - Move Semantics Basics
Move Semantics: Stealing Reasources Like a C++ Pro (It's Legal, I Promise)
views
| comments
Move Ctor & Assignment#
class MyIntVector {
int* ptr_ = nullptr;
std::size_t size_ = 0;
public:
MyIntVector(std::size_t initSize) {
if (initSize == 0)
return;
ptr_ = new int[initSize], size_ = initSize;
}
int& operator[](std::size_t idx) {
return ptr_[idx];
}
int operator[](std::size_t idx) const {
return ptr_[idx];
}
auto size() const {
return size_;
}
void swap(MyIntVector& another) noexcept {
std::ranges::swap(ptr_, another.ptr_);
std::ranges::swap(size_, another.size_);
}
MyIntVector(const MyIntVector& another) {
if (another.size_ == 0)
return;
std::unique_ptr<int[]> arr { new int[another.size_] };
std::memcpy(arr.get(), another.ptr_, another.size_ * sizeof(int));
ptr_ = arr.release();
size_ = another.size_;
return;
}
MyIntVector(MyIntVector&& another) noexcept :
ptr_ { std::exchange(another.ptr_, nullptr) },
size_ { std::exchange(another.size_, 0) } {}
MyIntVector& operator=(const MyIntVector& another) {
if (this == &another)
return *this;
MyIntVector temp { another };
swap(temp);
return *this;
}
MyIntVector& operator=(MyIntVector&& another) noexcept {
if (this == &another)
return *this;
MyIntVector temp { std::move(another) };
swap(temp);
return *this;
}
// If implemented as member-wise swap.
// MyIntVector &operator=(MyIntVector &&another) noexcept
// {
// swap(another);
// return *this;
// }
~MyIntVector() {
delete[] ptr_;
}
};
cpp-
const&&
is almost always useless. -
It’s up to you whether self-move is legal.
-
Move ctor & assignment for inheritance is very similar to copy ctor & assignment.
-
Move ctor & assignment should almost always be
noexcept
. -
Unlike copy, copy-and-swap idiom in move assignment usually doesn’t boost exception safety, but just simplifies code.
-
When to use
noexcept
:- move ctor/assignment
- dtor (default)
- deallocation functions, including
operator delete
. - swap operations
- those methods that obviously don’t throw any exception.
- hasher/
operator<=>
/operator==
- hasher/
Moved-from States#
- Generally, the least invariants for the class are:
- Destructible without error.
- Assignable without error.
- And when it’s assigned, it goes into a normal state again.
- Solutions:
- Make moved-from states satisfy current invariants.
- Enlarge the invariants, so that all member functions should consider moved-from states.
- Explicitly document what is allowed for moved-from objects and what is prohibited.
- You may use
assert
to check them.
- You may use
- Pay attention to operations on moved-from objects before it’s assigned with a new state.
Rule of Zero/Five#
-
Rule of Five: Explicitly declare copy ctor & assignment, move ctor & assignment, dtor.
-
Rule of Zero: Don’t explicitly declare any of them if possible.
-
Move-only class
- Have APIs to check whether they actually own resources, e.g.
std::thread::joinable()
. - Have APIs to release owned resources, e.g.
std::thread::join()
. - Range-based for loop cannot use
auto
/const auto
. - cannot use
std::initializer_list<MoveOnlyClass>
to initializeContainer<MoveOnlyClass>
.
- Have APIs to check whether they actually own resources, e.g.
Iterators for Move Semantics#
Person init_persons[] { "Tom", "Jerry" };
std::vector<Person> persons {
std::move_iterator { std::begin(init_persons) },
std::move_iterator { std::end(init_persons) },
};
cpp- You can also use move iterators on one-pass algorithms, e.g.
std::for_each