読者です 読者をやめる 読者になる 読者になる

【C++】private コンストラクタに、メンバ関数から呼ばれた外部の関数からアクセスする

かなり限定的ですが、タイトルのような状況にぶち当たったので。


コンストラクタを private にして、create() みたいな static メンバ関数を作ってオブジェクトを作成するというイディオムがあります。

class hoge {
  hoge() { }  // private コンストラクタ

public:
  // この関数を使わないと、hoge オブジェクトは作成できない
  static hoge* create_hoge() {
    static hoge h;
    return &h;
  }
};

シングルトンなんかを実装するときに使われるテクニックですね。

じゃあ次のようにしたらどうでしょうか。

// 何らかのオブジェクトを作って返す関数
template<class T>
T* create() {
  static T t;  // (1)
  return &t;
}

class hoge {
  hoge() { }

public:
  static hoge* create_hoge() {
    return create<hoge>(); // 外部の関数でオブジェクト作成
  }
};

オブジェクトの作成を外部の関数に委譲しました。
すると、(1)の場所で「private メンバにアクセスできねーよ」とコンパイラに怒られます。
create() と hoge クラスは何の関係もないので当然ですね。


では、どうするのかというと。
関数内クラスと継承を利用します。*1

template<class T>
T* create() {
  static T t;
  return &t;
}

class hoge {
  hoge() {}

public:
  static hoge* create_hoge() {
    // hoge を継承するクラスを関数内に作成
    struct hoge_impl : hoge {
      hoge_impl() : hoge() { }
    };
    return create<hoge_impl>();
  }
};

関数内でhoge_implというクラスを定義し、hogeを継承しています。
そして、create() にはこの hoge_impl オブジェクトを作ってもらいます。
hoge_impl のコンストラクタは public なので、create() は hoge_impl を作成できます。
hoge_impl へのポインタは hoge へのポインタに何の問題もなく変換できるので、create_hoge() の呼び出し元には無事 hoge オブジェクトへのポインタが返ります。
また、hoge_impl は craete_hoge() 内でしか使えないので、ほかの外部の関数などから hoge の private コンストラクタにアクセスすることはあり得ません。


これで、カプセル化を破ることなく外部の関数にオブジェクトを作ってもらうことができるようになりました。

(実際に自分が遭遇したケースは、上記の create() に当たるのがアロケータの construct() 関数でした。
デフォルトでは配置newを呼んでるだけなので、当初は直接配置newを呼んで対応してました。)

参考:
std::make_shared から private コンストラクタを呼び出す - 野良C++erの雑記帳

*1:ほかにも、クラス内クラスを利用する方法もあるそうです