dokee's site

Back

Modern C++ Basics - Move Semantics BasicsBlur image

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==

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.
  • 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 initialize Container<MoveOnlyClass>.

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
Modern C++ Basics - Move Semantics Basics
https://astro-pure.js.org/blog/c/modern-c-basics/move-semantics-basics
Author dokee
Published at March 11, 2025
Comment seems to stuck. Try to refresh?✨