C++: ムーブセマンティクス

最終更新:2026-08-31

レッスン44では、マルチスレッド同期について学びました。

ここでは、C++11の最重要機能——ムーブセマンティクスについて学びます。

ムーブセマンティクスを理解することは、C++のパフォーマンス最適化を理解することにつながります。


1. ムーブセマンティクスの基本

(1) 1.1 なぜムーブセマンティクスが必要?

問題: C++98/03では、オブジェクト(右辺値)を返す際に不要なコピーが発生。

▶ サンプル 2:STLコンテナの使用(難易度 ⭐)

TEXT 📖 参照専用
std::vector<int> createVector() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    return v; // ムーブセマンティクスにより最適化
}

std::vector<int> dest = createVector(); // コピーではなくムーブ!

ムーブセマンティクスの解決策: 「一時オブジェクト」から所有権を移転し、コピーを回避します。


(2) 1.2 左辺値と右辺値

分類 説明
左辺値(Lvalue) 名前があり、アドレスを持つ int x = 10;x
右辺値(Rvalue) 一時オブジェクト、名前なし 10x + 1、関数の戻り値

たとえ話:




2. 右辺値参照

(1) 2.1 基本的な概念

右辺値参照&& で表し、右辺値にバインドします。

▶ サンプル:右辺値参照(難易度 ⭐)

CPP
#include <iostream>

void process(int& x) {
    std::cout << "左辺値参照:" << x << std::endl;
}

void process(int&& x) {
    std::cout << "右辺値参照:" << x << std::endl;
}

int main() {
    int a = 10;
    process(a);       // process(int&) を呼び出し
    process(20);      // process(int&&) を呼び出し
    
    return 0;
}
▶ 試してみよう

(2) 2.2 std::move

std::move左辺値を右辺値参照にキャストし、「このオブジェクトを移動してもいい」という意図を表します。

▶ サンプル:std::moveの使用(難易度 ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <utility>

int main() {
    std::vector<int> v1 = {1, 2, 3};
    std::vector<int> v2 = std::move(v1); // ムーブ、v1の「中身」がv2へ移動
    
    std::cout << "v2のサイズ:" << v2.size() << std::endl; // 3
    std::cout << "v1のサイズ:" << v1.size() << std::endl; // 0(v1は空になる)
    
    return 0;
}
▶ 試してみよう
💡 ヒント:

  • std::move 自体は何も移動しません、型キャストのみ
  • 実際の移動はムーブコンストラクタムーブ代入演算子が行います



3. ムーブコンストラクタとムーブ代入

(1) 3.1 なぜ定義が必要?

問題: コンパイラ生成のデフォルトはディープコピー(クラスがリソースを持つ場合)。

解決策: ムーブコンストラクタとムーブ代入演算子を定義。


(2) 3.2 例:Stringクラスの実装(難易度 ⭐⭐⭐)

CPP
#include <iostream>
#include <cstring>

class String {
private:
    char* data;
    size_t length;

public:
    // 通常のコンストラクタ
    String(const char* str) {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
        std::cout << "構築:" << data << std::endl;
    }
    
    // コピーコンストラクタ(ディープコピー)
    String(const String& other) {
        length = other.length;
        data = new char[length + 1];
        strcpy(data, other.data);
        std::cout << "コピー構築:" << data << std::endl;
    }
    
    // ムーブコンストラクタ(ムーブ)
    String(String&& other) noexcept {
        data = other.data;       // ポインタを横取り
        length = other.length;
        other.data = nullptr;    // 元のオブジェクトを空にする(二重解放防止)
        other.length = 0;
        std::cout << "ムーブ構築..." << std::endl;
    }
    
    // デストラクタ
    ~String() {
        delete data;
    }
};

int main() {
    String s1("Hello");
    String s2 = std::move(s1); // ムーブコンストラクタを呼び出し
    
    return 0;
}

出力:

TEXT 📖 参照専用
構築:Hello
ムーブ構築...



4. ムーブセマンティクスのルール

(1) 4.1 五大特殊関数

クラスがデストラクタ、コピーコンストラクタ、コピー代入を定義した場合、ムーブコンストラクタムーブ代入を定義すべきです。

関数 説明
デストラクタ リソース解放
コピーコンストラクタ ディープコピー
コピー代入演算子 コピー代入
ムーブコンストラクタ ムーブ
ムーブ代入演算子 ムーブ代入

(2) 4.2 ゼアルール

推奨: スマートポインタでリソース管理すれば、五大関数を定義不要(コンパイラ生成のデフォルトで十分)。

TEXT 📖 参照専用
class Person {
    std::string name;    // stringはムーブ対応済み
    std::vector<int> scores; // vectorもムーブ対応済み
    // 五大関数の定義不要!
};



5. 完全転送

(1) 5.1 完全転送とは?

完全転送(Perfect Forwarding)は、関数の引数を元の型(左辺値/右辺値)のまま転送します。

▶ サンプル:std::forwardで完全転送(難易度 ⭐⭐⭐)

CPP
#include <iostream>
#include <utility>

void process(int& x) {
    std::cout << "左辺値参照..." << std::endl;
}

void process(int&& x) {
    std::cout << "右辺値参照..." << std::endl;
}

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

int main() {
    int x = 10;
    wrapper(x);  // 左辺値として転送
    wrapper(20); // 右辺値として転送
    
    return 0;
}
▶ 試してみよう


6. ムーブセマンティクスのパフォーマンス

▶ サンプル 1:コピーとムーブの比較(難易度 ⭐⭐)

CPP
#include <iostream>
#include <vector>
#include <chrono>

int main() {
    std::vector<std::vector<int>> v;
    
    // コピーの場合
    auto start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000; i++) {
        std::vector<int> temp(1000, 1);
        v.push_back(temp); // コピー!
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto copy_time = std::chrono::duration<double>(end - start).count();
    
    v.clear();
    
    // ムーブの場合
    start = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 10000; i++) {
        std::vector<int> temp(1000, 1);
        v.push_back(std::move(temp)); // ムーブ!
    }
    end = std::chrono::high_resolution_clock::now();
    auto move_time = std::chrono::duration<double>(end - start).count();
    
    std::cout << "コピー時間:" << copy_time << " 秒" << std::endl;
    std::cout << "ムーブ時間:" << move_time << " 秒" << std::endl;
    
    return 0;
}
▶ 試してみよう

▶ サンプル 3:ムーブ専用型(難易度 ⭐⭐⭐)

CPP
#include <iostream>
#include <utility>

class Resource {
private:
    int* data;
    size_t size;

public:
    Resource(size_t n) : size(n), data(new int[n]) {
        std::cout << n << " 要素を確保しました" << std::endl;
    }
    
    // ムーブコンストラクタ
    Resource(Resource&& other) noexcept 
        : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
        std::cout << "リソースをムーブしました" << std::endl;
    }
    
    // コピーコンストラクタを削除
    Resource(const Resource&) = delete;
    Resource& operator=(const Resource&) = delete;
    
    ~Resource() {
        if (data) {
            delete[] data;
            std::cout << size << " 要素を解放しました" << std::endl;
        }
    }
};

int main() {
    Resource r1(100);
    Resource r2 = std::move(r1);
    
    return 0;
}
▶ 試してみよう

出力:

TEXT 📖 参照専用
100 要素を確保しました
リソースをムーブしました
100 要素を解放しました

❓ よくある質問

Q:std::moveは必ずムーブする? A: いいえ。型がムーブコンストラクタを持たない場合、コピーコンストラクタが呼ばれます(ムーブできない)。


Q:ムーブ後のオブジェクトは? A:ムーブ後のオブジェクトは有効だが未規定の状態になります。値は保証されませんが、破棄は可能です。


Q:いつムーブセマンティクスを使う? A: - 大きなオブジェクトを返す場合(関数の戻り値)

  • 一時オブジェクトから所有権を移転する場合(std::move
  • コンテナへの要素追加(emplace_back

📖 まとめ

トピック 要点
右辺値参照 && で右辺値にバインド
std::move 左辺値を右辺値参照にキャスト
ムーブコンストラクタ リソースを横取り、高速
ムーブ代入 ムーブ代入
完全転送 std::forward で元の型のまま転送

📝 練習問題

  1. 初級(難易度 ⭐): vector<string> を作成し、push_back で文字列を追加し、出力を観察。次に emplace_back を使用。

  2. 中級(難易度 ⭐⭐): MyString クラスを実装(コピーコンストラクタとムーブコンストラクタ含む)、main関数で std::move でムーブセマンティクスを試し、コンストラクタの呼び出しを観察。

  3. 上級(難易度 ⭐⭐⭐): ムーブ専用型(unique_ptr のような)を実装し、コピーコンストラクタとコピー代入を削除し、ムーブコンストラクタとムーブ代入を使用。テストコードを書く。



次のレッスン:スマートポインタ応用(#46)

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%