この記事のポイント

  • explicitコンストラクタの役割と設計意図を整理
  • レビューで暗黙変換の危険性をどう検出するか解説
  • 実務コード改善例・レビュー観点を提示

C++におけるコンストラクタ暗黙変換とは何か

C++のコンストラクタはデフォルトで以下の暗黙型変換を許容します。

class UserId {
public:
    UserId(int id) : id_(id) {}
private:
    int id_;
};

void fetch(UserId user);

fetch(42);  // 暗黙にUserId(42)へ変換される

暗黙変換の問題点

問題 内容
意図不明 何が自動変換されたか読者に見えない
型安全崩壊 実質int型API化する危険
オーバーロード曖昧性 複数の暗黙変換候補出現

レビュー観点の大前提

「コンストラクタ暗黙変換を極力許容しない」


explicit指定とは何をするものか

C++ではコンストラクタにexplicitを付けることで暗黙変換禁止できます。

class UserId {
public:
    explicit UserId(int id) : id_(id) {}
};
  • 明示呼び出しのみ許可
fetch(UserId(42));  // OK
fetch(42);          // コンパイルエラー

なぜexplicit指定がレビュー標準になるのか

① 意図的な変換呼び出しを強制できる

  • 呼び出し側が責任を持って型構成を記述

② 設計責務の型明示化

  • 「UserIdはintとは別の責務」と型設計を分離可能

③ 拡張時の安全性確保

  • 後の引数追加・仕様変更に耐性を持つ

④ API設計の境界を強化

  • 入力型流用を設計で明確ブロック可能

レビューで確認するポイント

確認対象 レビュー判断
単項コンストラクタ 原則explicit指定必須
暗黙変換目的の除外明示 明文化が必要
API公開範囲 呼び出し責務が明示されているか
拡張性 引数変更時に破壊影響を防止できる設計か

良い実装例

class UserId {
public:
    explicit UserId(int id) : id_(id) {}

private:
    int id_;
};

void fetch(UserId user);

// 呼び出し側
fetch(UserId(42));  // 明示変換のみ許容

良い設計理由

  • API境界の型責務が明確
  • 呼び出し側の意図記述が必要
  • 後の保守性が高まる

良くない実装例: ケース1

悪い例:explicit無しの暗黙許容
class UserId {
public:
    UserId(int id) : id_(id) {}
};

fetch(42);
@Reviewer
単項コンストラクタにexplicitを付けてください。暗黙変換は型安全性を崩壊させます。

改善例

class UserId {
public:
    explicit UserId(int id) : id_(id) {}
};

fetch(UserId(42));

ケース2: オーバーロード曖昧化

悪い例:曖昧オーバーロード発生
class UserId {
public:
    UserId(int id) {}
};

void fetch(UserId user);
void fetch(int rawId);

fetch(42);  // どちら呼ばれる?
@Reviewer
オーバーロード混在時は暗黙変換排除必須です。意図解釈不能になります。

改善例

class UserId {
public:
    explicit UserId(int id) {}
};

ケース3: 設計責務不明化

悪い例:実質int型API化
void fetch(UserId user);
fetch(42);
@Reviewer
API設計責務がint流用になっています。型安全設計原則に違反します。

改善例

fetch(UserId(42));

ID系・識別子系は特にexplicit必須

  • ドメイン語彙を型で表現する設計文化

ケース4: 複数引数は通常explicit不要

class Point {
public:
    Point(int x, int y) : x_(x), y_(y) {}
};

Point p = {10, 20};  // OK
@Reviewer
多項コンストラクタは暗黙変換危険度が低いため通常explicit不要です。

レビュー基準

  • 単項→原則explicit
  • 多項→通常省略可

ケース5: 暗黙変換を設計的に許可する場合

class Meter {
public:
    Meter(double m) : m_(m) {}

    // 設計上doubleからの暗黙変換を許可
};
@Reviewer
暗黙変換許容時は設計責務説明をコメントに残してください。

改善例

class Meter {
public:
    // 物理量としてdouble直流入許可する設計意図
    Meter(double m) : m_(m) {}
};

ケース6: explicitでも初期化リスト対応確認

class UserId {
public:
    explicit UserId(int id) : id_(id) {}
};

UserId u = {42};  // C++11以降OK
@Reviewer
初期化リストでも意図明示は維持されています。OKです。

{}初期化でもexplicitは機能する

  • 安全性損なわない
  • レビュー時誤解注意

explicitレビューの実務意義整理

課題 explicitで防止できるリスク
型崩壊 API流用誤用
拡張不能設計 引数追加破壊
意図不明API ドメイン責務消失
メンテ不能化 保守時の副作用膨張

観点チェックリスト


まとめ

レビューアーは常に以下を自問する必要があります:

「このコンストラクタ、誰が勝手に変換できるのか?」

  • explicitは型安全設計の要
  • 暗黙変換排除文化が設計拡張耐性を最大化

C++レビューの基本動作として、explicit確認は常に初動レビューに組み込むべき対象です。