LoginSignup
20
16

More than 5 years have passed since last update.

C++用privateっぽいnamespace

Last updated at Posted at 2015-12-18

この記事はQiitaのC++ Advent Calendar 2015の19日目の記事です。


【2015/12/26追記】
開発中のプログラムへ適用してみました。私のプログラムでは使わないことにしました。orz
ごめんなさい。思ったより制約が多いです。

無名名前空間内に配置された「定義」は他のコンパイル単位からアクセスできません(=外部リンケージを持たない)。そのため、クラスのメンバ関数を全てのコンパイル単位でコンパイルされるようヘッダへ入れる必要があります。・・・①
通常のクラスでそれはあんまりですので、EXTERNAL/INTERNAL空間に入れるのはテンプレートだけにした方が良いです。(どうせヘッダに入れますから。)
ということは、隠したいクラス・テンプレートでも、通常クラスから呼び出すものはEXTERNAL空間に置くことになり、隠せません。・・・②

それでも大半は隠せるのですが、gccは外部リンケージを持たないものを外部リンケージを持つクラスの基底クラスやメンバにすると、危険と判断し警告を出します。・・・③
この警告を禁止するオプションがつい先日追加されたようでドキュメントにも既に記載されてますが、まだ使えませんでした。(MinGW 5.2.0)

私のプログラムでは、一部のテンプレートについては頑張って実装部を1つのコンパイル単位でのみコンパイルするようにしているのですが、そのため①にハマりました。
隠すことを強制するためにコンパイル時間が伸びるのはどうかと思います。更に②や③の問題もあるので、使用を断念しました。

「4.さいごに」で参照している掲示にて複数の方が言っている通り、内部実装についてはboostのdetailやimplのように別空間にして「実装」である旨を示すことが現実的なようです。残念無念。

お騒がせしました。


1.はじめに

昨日はこれこれを投稿したのですが、つい先日面白いネタを拾ったので、枯れ木も山の賑わいでもう1ネタ投稿してみます。

今C++のライブラリを開発中なのですが、内部用のクラス群をユーザが使えないようにしたいと思い立ちました。内部用として設計しているため、ユーザ向けの各種ガードはしてないし、それらのクラスは自由に設計変更したいので、できればユーザが簡単には使えないようにしておきたいわけです。(privateによる実装の隠蔽と同じ目的です。クラス・テンプレートは明示的実体化できない場合ヘッダで実装するしかないのに、何故できないんだ?)

C++にprivateなnamespace機能はないので、半ば諦めつつ良い方法を知っている人いないかなとteratailで質問したところ、内部用的な名前の名前空間に入れる案を頂けました。「やはり名前で区別するしかないのか?でも需要があるならヒントもあるかも」とフト思いついてprivate namespaceで検索したところ、namespaceでprivateはやっぱりできないと考えるべきなのかしら。がでてきました!!当投稿のアイデアはここから頂いたものです。

2.privateっぽいnamespaceを実現するアイデア

①無名名前空間A内に内部用名前空間internalを定義します。
②そして、それを隠すために別の名前空間internal'を定義します。これは元のinternalと同じ名前の別空間を無名名前空間Aと同じレベルに定義します。これにより、外部からのアクセスをinternal'の方へ誘導し、①をprivateっぽくします。

具体的には下記のようにします。

namespace my_library
{
    namespace internal {}   // 外部アクセス誘導用
    namespace
    {
        namespace internal  // この名前空間内はprivate化される
        {
            // ここで内部用クラスや関数を定義する。
            class Foo {};
        }

        // ここで公開したい外部用クラスや関数を定義する。
        class Bar : public internal::Boo {};
    }
}

int main()
{
    my_library::Bar bar; // これはOK
//  my_library::internal::Foo foo; // これはエラー
    return 0;
}

外部アクセス誘導用のinternalが無かった場合、無名名前空間は名前無しでアクセスするため、my_library::internal::FooにてFooを使えます。
しかし、my_libraryの直下に空っぽのinternalがあると、これが邪魔してFooへアクセスできなくなるという寸法です。(誰が思いついたのでしょう。頭いいです。って、どうやらここの補足コメントでhaporuさんが思いついたようです。これが最初かどうかまでは確認できませんが。)

そして、名前空間は分割できるため、Fooを使いたい人は下記のように名前空間に入れればアクセスできます。名前空間は複数のブロックに分割しても問題ありませんね。つまり、複数ヘッダにまたがってアクセスすることも可能です。通常の名前空間と使い方は同じです。

namespace my_library
{
    namespace
    {
        // ここからinternalへアクセス可能
        void UsingFoo()
        {
            internal::Foo foo;
        }
    }
}

ということは、上記をユーザ・ソースで定義するだけで破れますね。でも、それなりの知識がないとできないし、ライブラリの名前空間内にユーザ・クラスや関数を定義するということはユーザ・ソースの可読性が大きく劣化するので、かなり明確に「使用禁止」を示せる方法と思います。

3.マクロを使って解りやすく

前節の書き方では、何をやっているのか、パッと見て想像がつきません。
そこで、少し分かりやすい書き方をできるようにマクロを定義してみました。

#define EXTERNAL    namespace internal {}; namespace
#define INTERNAL    namespace internal

こんな風に使います。

namespace my_library { EXTERNAL
{
    INTERNAL
    {
        // ここで内部用クラスや関数を定義する。
        class Foo {};
    }

    // ここで公開したい外部用クラスや関数を定義する。
    class Bar : public internal::Foo {};
}}  // ←閉じ括弧が2つ必要なので要注意!!orz

EXTERNALはトップ・レベルの名前空間に付けるおまじないです。
この名前空間は}}で終了します。(きっと時々}を1つ書き忘れます。何か対策があると良いのですが...)

MSVC 2015とMinGW 4.9.2とMinGW 5.2.0でビルド確認しています。

4.最後に

ストロング・ブレーク程ではないと思いますが、議論のある使い方と思います。
ですので、業務では使う前に関係者の了解を得ることをお薦めします。
プロジェクトの特性によっては使うべきでないケースもありますので。

因みに、private namespaceで検索すると、こんなやり取りが見つかりました。賛成が多い方の回答に、実装用名前空間(例えばdetail)に入れておけば「ユーザはそれを使ってはいけないことを知っている」みたいなことが書かれてました。えっ、知りませんでした。orz
boostで初めてdetail名前空間を見ましたが、boostはimpl名前空間も使ってます。implは実装だから使うと将来ハマるとは思ったけど、detailはイマイチなんなのかよく判らなかったですね。

あと、こんな議論もありました。detailの代わりにprivateを使いたいけどprivateはキーワードなので使えないとか、detailよりinternalかprivateがいいとか、detailはboostが使ったから広まったと思うが、最近implを見るし自分もimplを使っているなどなど。

20
16
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
20
16