C++コピー/ムーブ構築・代入は意図を明示する設計責務|レビューで資源管理と所有権の整合性を確認する技法
この記事のポイント
- コピー・ムーブ構築/代入4種の意図を整理
- レビューで意図不一致や自動生成暴走をどう検出するか解説
- 実務コードの改善例・レビュー観点を提示
C++特殊メンバ 4種の構造整理
C++では以下の特殊メンバが存在する。
種別 | シグネチャ | 役割 |
---|---|---|
コピーコンストラクタ | ClassName(const ClassName&) |
新規インスタンスへコピー初期化 |
ムーブコンストラクタ | ClassName(ClassName&&) |
新規インスタンスへ所有権移譲初期化 |
コピー代入演算子 | ClassName& operator=(const ClassName&) |
既存インスタンスへコピー代入 |
ムーブ代入演算子 | ClassName& operator=(ClassName&&) |
既存インスタンスへ所有権移譲代入 |
:::info
ここがレビュー本質
この4種は「資源責務の転送ルール」そのものである
:::
自動生成任せの危険性
① 資源所有権が二重管理に陥る
FileHandle a(3);
FileHandle b = a; // fd 3 が重複保持
② ムーブできない型にムーブ想定代入が暴走
③ 保守拡張時に不整合設計が露呈
- 成員追加で自動生成が期待とずれる
C++11以降の正しい設計表現:明示定義
class FileHandle {
public:
explicit FileHandle(int fd) : fd_(fd) {}
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&&) noexcept = default;
FileHandle& operator=(FileHandle&&) noexcept = default;
private:
int fd_;
};
:::tip
完全明示がレビュー文化の中核
- 許す責務 →
=default
- 許さぬ責務 →
=delete
:::
良い実装例
class Socket {
public:
explicit Socket(int fd) : fd_(fd) {}
Socket(const Socket&) = delete;
Socket& operator=(const Socket&) = delete;
Socket(Socket&&) noexcept = default;
Socket& operator=(Socket&&) noexcept = default;
private:
int fd_;
};
良い設計理由
- 所有権移譲のみを許可
- 二重所有を静的封止
- レビュー者が設計意図を即理解可能
レビュー観点
レビューアーは以下を確認する。
- コピー/ムーブ全4種が明示定義されているか
- 所有権設計と一致しているか
- delete適用は両系統(構築+代入)で一貫しているか
- default使用箇所は設計理由が説明可能か
- move専用型でもdeleteがセットで書かれているか
良くない実装例: ケース1
class Logger {
public:
Logger() = default;
Logger(const Logger&) = delete;
};
@Reviewerコピー代入演算子もdelete指定が必要です。不完全禁止です。
改善例
Logger& operator=(const Logger&) = delete;
ケース2: ムーブ設計でコピー禁止が抜け
class Connection {
public:
Connection(Connection&&) noexcept = default;
};
@Reviewerムーブ設計ならコピー禁止(delete)も併記してください。
改善例
Connection(const Connection&) = delete;
Connection& operator=(const Connection&) = delete;
ケース3: 実質所有権型で禁止漏れ
class FileHandle {
public:
explicit FileHandle(int fd) : fd_(fd) {}
};
@Reviewer所有権型ではコピー禁止(delete)を必ず明示してください。
改善例
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
ケース4: 空実装でdefault使わず
class Point {
public:
Point() {}
};
@Reviewerdefaultで意図明示してください。
改善例
Point() = default;
ケース5: C++03流 private禁止残存
class Task {
private:
Task(const Task&);
Task& operator=(const Task&);
};
@ReviewerC++11以降はdeleteで統一してください。
改善例
Task(const Task&) = delete;
Task& operator=(const Task&) = delete;
ケース6: default/delete理由が可視化不足
class Manager {
public:
Manager(const Manager&) = delete;
};
@Reviewerなぜコピー禁止か設計理由をコメント付記してください。
改善例
// シングルトン設計のためコピー禁止
class Manager {
public:
Manager(const Manager&) = delete;
Manager& operator=(const Manager&) = delete;
};
コピー/ムーブ構築・代入レビューの設計防止効果
防止対象 | 設計効果 |
---|---|
所有権混乱 | 二重管理封止 |
API誤用 | 呼び出し側設計強制 |
継承事故 | 不正継承防止 |
保守崩壊 | 拡張安全保証 |
観点チェックリスト
まとめ
レビューアーの思考は常にこう整理する:
「この型の資源責務転送ルールは明示されているか?」
- default/delete文化は設計意図自己説明言語
- コピー/ムーブは所有権設計を読み取る起点
「暗黙生成放置ゼロ文化」こそC++レビュー技術の礎です。