8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

C++11で導入されたムーブセマンティクスは、不要なコピーを避けてパフォーマンスを劇的に改善する機能。

実測結果を見てほしい。無限倍だよ?

実測結果:

Copy time: 41568 us
Move time: 0 us
Speedup: ∞x

コピーとムーブの違い

コピー

std::vector<int> v1(10000, 42);
std::vector<int> v2 = v1;  // コピー
// v1とv2は別々のメモリを持つ
// 10000要素分のメモリ確保 + コピー

ムーブ

std::vector<int> v1(10000, 42);
std::vector<int> v2 = std::move(v1);  // ムーブ
// v2はv1のメモリを「奪う」
// v1は空になる(有効だが未規定の状態)
// メモリ確保もコピーもなし

std::move

std::movervalue参照にキャストするだけで、実際にムーブするわけではありません。

std::string s1 = "Hello";
std::string s2 = std::move(s1);  // s1をrvalue参照にキャスト → ムーブコンストラクタが呼ばれる

std::cout << s1;  // "" (空、有効だが未規定)
std::cout << s2;  // "Hello"

// ムーブ後も再代入は安全
s1 = "New value";

ムーブコンストラクタの実装

class Buffer {
private:
    int* data;
    size_t size;
    
public:
    // ムーブコンストラクタ
    Buffer(Buffer&& other) noexcept 
        : data(other.data), size(other.size) 
    {
        other.data = nullptr;  // 所有権を奪う
        other.size = 0;
    }
    
    // ムーブ代入演算子
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data;      // 既存リソースを解放
            data = other.data;  // 所有権を奪う
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
};

Rule of Five

5つの特殊メンバ関数のいずれかを定義したら、全て定義すべきです。

class MyClass {
public:
    ~MyClass();                              // 1. デストラクタ
    MyClass(const MyClass&);                 // 2. コピーコンストラクタ
    MyClass& operator=(const MyClass&);      // 3. コピー代入演算子
    MyClass(MyClass&&) noexcept;             // 4. ムーブコンストラクタ
    MyClass& operator=(MyClass&&) noexcept;  // 5. ムーブ代入演算子
};

不要なら明示的に:

class NonCopyable {
public:
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    NonCopyable(NonCopyable&&) = default;
    NonCopyable& operator=(NonCopyable&&) = default;
};

noexcept の重要性

ムーブコンストラクタには必ず noexceptを付けましょう

// ✓ 良い
Buffer(Buffer&&) noexcept;

// ❌ 悪い
Buffer(Buffer&&);  // noexceptなし

理由: std::vectorの再アロケーション時、ムーブコンストラクタがnoexceptでないと、例外安全性のためにコピーが使われてしまいます

Perfect Forwarding

テンプレート関数でlvalue/rvalueを正しく転送するにはstd::forwardを使います。

void process(int& x)  { std::cout << "lvalue\n"; }
void process(int&& x) { std::cout << "rvalue\n"; }

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));  // 完全転送
}

int x = 42;
wrapper(x);           // lvalue → process(int&)
wrapper(100);         // rvalue → process(int&&)
wrapper(std::move(x)); // rvalue → process(int&&)

vectorでの活用

std::vector<std::string> vec;
std::string s = "Hello";

// コピー
vec.push_back(s);           // sはそのまま
std::cout << s;             // "Hello"

// ムーブ
vec.push_back(std::move(s)); // sは空になる
std::cout << s;              // "" (空)

// 直接構築(emplace_back)
vec.emplace_back("World");   // コピーもムーブもなし

関数の戻り値

std::vector<int> createVector() {
    std::vector<int> v(10000);
    return v;  // RVOまたはムーブ
}

auto v = createVector();  // コピーは発生しない

RVO (Return Value Optimization) により、多くの場合コピーもムーブも省略されます。

注意: return std::move(v);と書く必要はありません。逆にRVOが無効化される可能性があります。

いつムーブを使うか

✓ 使うべき場面

// 1. 不要になったオブジェクト
std::string result = std::move(temp);

// 2. コンテナへの挿入
vec.push_back(std::move(obj));

// 3. 所有権の移動
std::unique_ptr<T> p2 = std::move(p1);

✗ 使うべきでない場面

// 1. 後で使うオブジェクト
std::move(important_data);  // ダメ!

// 2. const オブジェクト
const std::string s = "Hello";
std::move(s);  // コピーになる(constなのでムーブできない)

// 3. 関数のreturn
return std::move(local);  // 不要、RVOを阻害する

パフォーマンス実測

const int N = 100000;
std::vector<std::string> source(N, std::string(1000, 'x'));

// コピー
std::vector<std::string> copy = source;
// → 41,568 μs

// ムーブ
std::vector<std::string> moved = std::move(source);
// → 0 μs (ほぼ瞬時)

100MBのデータがムーブなら**ポインタのコピー(数バイト)**で済みます。

まとめ

概念 説明
ムーブ リソースの所有権を移動
std::move rvalue参照へのキャスト
&& rvalue参照
noexcept ムーブに必須
std::forward 完全転送
RVO 戻り値の最適化

ポイント:

  1. 不要になったオブジェクトはstd::move
  2. ムーブコンストラクタにはnoexcept
  3. returnstd::moveは不要
  4. ムーブ後のオブジェクトは使わない
// 基本パターン
std::string s = "Hello";
vec.push_back(std::move(s));  // sはもう使わない
8
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?