web-dev-qa-db-ja.com

C ++拡張可能な名前空間-宣言を強制的にグローバル名前空間に戻す方法

それらを参照するヘッダーに必要なすべての依存関係を含めることは、優れたプログラミングスタイルです。多くの場合、これにはSTDおよびグローバル名前空間(cstdioなど)に配置された宣言が含まれます。ただし、2番目のプログラマがそのようなインクルードファイルを新しい名前空間にラップして最初のインクルードファイルをカプセル化しようとすると、問題が発生します。

このシナリオを念頭に置いて、宣言をグローバル名前空間に強制する方法はありますか?概念的な例として(これは実際にはコンパイルされません):

_foo.h --> original definition
// force global namespace, this is redundant when foo is in the global space
//   but could be important if foo included into another namespace.
namespace :: { 
  #include <cstdio>
}
namespace foo {
  FILE *somefile;
}


bar.h --> New file, by a new programmer, encapsulating foo.
namespace bar {
  # include "foo.h"
  FILE *somefile;  // bar:somefile, as opposed to bar::foo::somefile
}
_

推定の_namespace ::{}_がないと、_foo::FILE_とfoo::printf()が必要なだけで、_::FILE_と::printf()のような宣言になります。

boost のような一般的なライブラリは、明示的な階層を定義することでこれを解決します。これは、fooがそれがbarの一部になることを知っている必要があり、グローバルネームスペースがローカルネームスペース定義の外部に含まれていることを示します。

ただし、拡張性を高めるには、fooを直接使用できる必要があります。OR fooまたはそのインクルードを変​​更する必要なく、新しい名前空間でfooをラップします。

誰かがこれを達成する方法を知っていますか?

6
bgulko

ヘッダーを名前空間に含めることは、さまざまな前提を破る傾向があるため、行うべきではありません。頭に浮かぶ問題は次のとおりです。

  • それはガードを含むブレークします。ヘッダーを一度しかインクルードできず、誤った名前空間にインクルードすると、他のコードは同じコンパイル単位内の正しい名前空間にインクルードできません。あるいは、逆に言うと、すでにライブラリに含まれている場合は、別の場所に再度含めることはできません。例えば。:

    someLib/A.h

    #ifndef SOMELIB_A_H_
    #define SOMELIB_A_H_
    // assume this will be included into namespace someLib
    class A {};
    #endif
    

    someLib/B.h

    #ifndef SOMELIB_B_H_
    #define SOMELIB_B_H_
    namespace someLib
    {
        # include "A.h"
        int b(const A& a) { return 42; }
    }
    #endif
    

    usercode.cpp

    // defines someLib::b and somelib::A
    #include <somelib/B.h>
    // include A into global namespace
    // – won't work because someLib/A.h was already included
    #include <somelib/A.h>
    #include <iostream>
    int main()
    {
        std::cout << someLib::b(A()) << '\n';
        return 0;
    }
    

    #include <someLib/A.h>はメインプログラムでは何もせず、何も含まれていないため、これは失敗します。したがって、Aはメインプログラムでは定義されていません。ファイルが逆に含まれている場合、someLib::b宣言はグローバルスコープのシンボルも参照できるため、ファイルは機能します。ただし、適切に設計されたライブラリでは、インクルードの順序は重要ではありません。

  • リンクできなくなります。ほとんどのヘッダーファイルには、プライベートな実装の詳細を含む、関連する.cppファイルがあります。ヘッダーを別の名前空間に含めると、実装とリンクできません。

  • マクロが壊れます。ライブラリが提供するマクロがライブラリにコールバックする場合、どの名前空間から使用されるかわからないため、常に完全な名前空間修飾名を使用する必要があります。シンボルfoo::bar::BazExceptionqux::BazExceptionとして含めることができた場合、次のような完全に妥当なマクロ

    #define FOO_BAR_BAZEXCEPTION(expr) foo::bar::BazException(expr, __FILE__, __LINE__)
    

    動作しなくなります。

  • これは、関数とその引数が名前空間を共有する場合にのみ機能する、引数に依存するルックアップを壊します。

名前空間にファイルを含めることがこのような恐ろしい考えであり、修正するよりも多くのものを壊してしまう場合、代替策は何ですか?

まず、名前空間は優れています。それらは、たとえ非常にオープンであっても、アーキテクチャ全体に構造を与えるのに役立ちます。名前空間を使用するのは、名前の衝突を回避するための便利な階層を課すだけでなく、引数に依存するルックアップなどの一部の機能がそれに依存しているためです。標準ライブラリがいくつかのシンボルをグローバルスコープに入れる唯一の理由は、Cとの互換性です。それでも、std::名前空間を通じてシンボルにアクセスできます。

名前空間が不便に長くなる可能性があると感じる人もいます。 using foo::bar::Bazで必要なスコープで短いエイリアスを定義できるため、これは問題ありません。これは、シンボルをグローバルスコープに配置するときの解決策でもあります。エイリアスは元の完全修飾名を非表示にしないため、上記のすべての問題は当てはまりません。 usingディレクティブ には多くの用途があり、名前空間、タイプ名、変数やメンバー関数などの値にエイリアスを設定できます。

ファイルをインクルードするときは、常に適切に名前空間が付けられていると想定してください。したがって、独自の名前空間を開く前に、それをグローバルスコープに含めてください。ヘッダーが独自のプロジェクトからのものである場合でも、それらが独自の名前空間を宣言していることを確認してください。これは、名前空間の構造を修正すると、変更がかなり困難になることも意味します。しかし、それは良いことです。パブリックインターフェイス(名前空間構造を含む)を変更すると、依存するすべてのコードも変更する必要があります。 「優れた拡張性」がこの構造をあきらめる必要があるかどうかはわかりません。

9
amon

簡単に言えば、名前空間の階層は設計時に静的に定義されるように設計されており、広範な設計では使用されていないようです。つまり、設計後の他の名前空間にラップすることはできません。したがって、これらは他の人のコードによる名前空間汚染を封じ込めるのではなく、自分のコードがグローバル空間を汚染するのを防ぐためのツールです。この制限は必要ではないと私は思いますが、それは私の即時の調査を明らかにします。

いくつかの追加作業により、部分的な解決策が得られましたが、簡単に言えば、名前空間はネストされるように設計されていませんafter設計。これらをこの方法で使用することは、可能であっても、設計意図に反するため、悪い考えです。とは言っても、それを実現する方法がここにあります。

リンケージに関する問題のほとんどは、次の2つの方法で解決できると思います。

  1. 非インラインインクルードがグローバル名前空間をホームとすることを確認します。
  2. インライン/ヘッダーベースの名前空間のみを拡張します。

最初の方法は、名前空間ごとにname_ext.hファイルを作成し、それをグローバルスコープに含めることで実行できます。派生名前空間は、依存するすべてのname_ext.hを含む独自のderived_ext.hを持ちます。したがって、複数のinclude-barrierを使用して、グローバルインクルードが確実にグローバル名前空間に置かれるようにします。これにより、バイナリライブラリのリンケージが保持されます。 A namespace :: {}既存の名前空間ブロック内からグローバル名前空間のホーム定義への拡張は、これをよりエレガントに修正します。

2番目のポイントは、.cppファイルのコンパイル時リンケージを扱います。私はこれをテストしましたが、うまくいきます。

残っている唯一の問題は、マルチホームクラスの問題です。動的なインクルードバリアに対する実質的なプリプロセッササポートがなければ、マルチホームの名前空間を作成できなくなります。これは、最終的には設計の直交性にとって実際の問題です。

ただし、これはインクルードファイルに完全に含まれる名前空間に限定されます。

いずれにせよ、この方法で名前空間を使用することは、可能であっても、現在の設計哲学とは正反対です。

2
bgulko

単純なことはしないでください。

ヘッダーを名前空間に含めることは馬鹿げたことであり、行うべきではありません。ヘッダーの名前空間に満足できない場合は、作成者に不満を言うか、ヘッダーをフォークします。ヘッダーを名前空間に含めることは、まったく効果がなく、無意味です。

2
DeadMG