7

名前の競合を避けるために、C++ ライブラリは名前空間を使用する必要があることは理解していますが、既に次のことを行う必要があるためです。

  1. #include正しいヘッダー (または、使用するクラスを前方宣言します)
  2. これらのクラスを名前で使用する

これら 2 つのパラメーターは、名前空間によって伝達される同じ情報を推測しないでください。名前空間を使用すると、3 番目のパラメーター (完全修飾名) が導入されるようになりました。ライブラリの実装が変更された場合、変更が必要になる可能性があることが3 つあります。これは、定義上、ライブラリ コードと私のコードとの間の結合の増加ではありませんか?


たとえば、Xerces-C を見てください。Parser名前空間内で呼び出される純粋仮想インターフェイスを定義しますXERCES_CPP_NAMESPACEParser適切なヘッダー ファイルをインクルードし、名前空間をインポートするか、宣言using namespace XERCES_CPP_NAMESPACE/定義の前にXERCES_CPP_NAMESPACE::.

コードが進化するにつれて、別のパーサーを優先して Xerces を削除する必要があるかもしれません。私は、純粋仮想インターフェースによるライブラリ実装の変更から部分的に「保護」されています (ファクトリを使用してパーサーを構築する場合はなおさらです) が、Xerces から別のものに切り替えるとすぐに、次のことを行う必要があります。私のコードをくまなく調べて、すべての私のコードとコードを変更してusing namespace XERCES_CPP_NAMESPACEくださいXERCES_CPP_NAMESPACE::Parser


最近、既存の便利な機能をライブラリに分割するために既存の C++ プロジェクトをリファクタリングしたときに、これに遭遇しました。

foo.h

class Useful;  // Forward Declaration

class Foo
{
public:

    Foo(const Useful& u);
    ...snip...

}

foo.cpp

#include "foo.h"
#include "useful.h" // Useful Library

Foo::Foo(const Useful& u)
{
    ... snip ...
}

当時、ほとんど無知 (そして部分的に怠惰) が原因で、 のすべての機能はuseful.libグローバル名前空間に配置されていました。

の内容が大きくなるuseful.libにつれて (そしてより多くのクライアントがこの機能を使用し始めた)、 からすべてのコードをuseful.libと呼ばれる独自の名前空間に移動することが決定されました"useful"

クライアント.cppファイルは簡単に修正できますusing namespace useful

foo.cpp

#include "foo.h"
#include "useful.h" // Useful Library

using namespace useful;

Foo::Foo(const Useful& u)
{
    ... snip ...
}

しかし、.hファイルは本当に労働集約的でした。using namespace useful;ヘッダー ファイルを挿入してグローバル名前空間を汚染する代わりに、既存の前方宣言を名前空間にラップしました。

foo.h

namespace useful {
    class Useful;  // Forward Declaration
}

class Foo
{
public:

    Foo(const useful::Useful& u);
    ...snip...
}

数十 (そして数十) のファイルがあり、これは大きな苦痛でした! それほど難しいことではなかったはずです。明らかに、設計および/または実装のいずれかで何か間違ったことをしました。

ライブラリ コードが独自の名前空間にある必要があることはわかっていますが、ライブラリ コードがグローバル名前空間にとどまり、代わり#includes?

4

7 に答える 7

10

あなたの問題は、名前空間自体ではなく、主に名前空間を(ab)使用していることが原因であるように思われます。

  1. 最小限に関連する多くの「もの」を1つの名前空間に投げ込んでいるように思われます。ほとんどの場合(それに到達すると)、同じ人によって開発されたためです。少なくともIMOの名前空間は、一連のユーティリティが同じ人物によって作成されたという偶然だけでなく、コードの論理的な編成を反映する必要があります。

  2. 名前空間名は通常、衝突の最も遠い可能性以上のものを防ぐために、かなり長く説明的なものにする必要があります。たとえば、私は通常、自分の名前、書かれた日付、および名前空間の機能の簡単な説明を含めます。

  3. ほとんどのクライアントコードは、名前空間の実際の名前を直接使用する必要はありません(多くの場合、使用すべきではありません)。代わりに、名前空間エイリアスを定義する必要があり、ほとんどのコードではエイリアス名のみを使用する必要があります。

ポイント2と3を組み合わせると、次のようなコードになります。

#include "jdate.h"

namespace dt = Jerry_Coffin_Julian_Date_Dec_21_1999;

int main() {

    dt::Date date;

    std::cout << "Please enter a date: " << std::flush;
    std::cin>>date;

    dt::Julian jdate(date);
    std::cout   << date << " is " 
                << jdate << " days after " 
                << dt::Julian::base_date()
                << std::endl;
    return 0;
}

これにより、クライアントコードと日付/時刻クラスの特定の実装との間の結合が削除されます(または少なくとも大幅に削減されます)。たとえば、同じ日付/時刻クラスを再実装したい場合は、それらを別の名前空間に配置し、エイリアスを変更して再コンパイルするだけで、一方と他方を切り替えることができます。

実際、私はこれを一種のコンパイル時のポリモーフィズムメカニズムとして使用しました。一例として、私は小さな「display」クラスのいくつかのバージョンを作成しました。1つはWindowsリストボックスに出力を表示し、もう1つはiostreamを介して出力を表示します。次に、コードは次のようなエイリアスを使用します。

#ifdef WINDOWED
namespace display = Windowed_Display
#else
namespace display = Console_Display
#endif

残りのコードはを使用するだけdisplay::whateverなので、両方の名前空間がインターフェイス全体を実装している限り、残りのコードをまったく変更せずに、また基本クラスへのポインター/参照を使用することによる実行時のオーバーヘッドなしに、どちらかを使用できます実装用の仮想関数を使用します。

于 2010-02-04T20:07:08.260 に答える
9

名前空間はカップリングとは関係ありません。useful::UsefulClassと呼んでも、単にと呼んでも、同じ結合が存在しますUsefulClass。すべての作業をリファクタリングする必要があったという事実は、コードがライブラリにどの程度依存しているかを示すだけです。

転送を容易にするために、forwardヘッダーを作成することもできます (STL にはいくつかありますが、ライブラリで確実に見つけることができます)usefulfwd.hだけが転送定義されたライブラリ インターフェイス (または実装クラスなど、必要なもの) のようにします。しかし、これはカップリングとは何の関係もありません。

それでも、カップリングと名前空間は無関係です。バラは他の名前と同じくらい甘い香りがするでしょうし、あなたのクラスは他の名前空間と同じように結合されています.

于 2010-02-04T19:58:39.260 に答える
6

(a) ライブラリのインターフェース/クラス/関数

あなたがすでに持っている以上のものではありません。namespace-ed ライブラリ コンポーネントを使用すると、名前空間の汚染を防ぐことができます。

(b) 名前空間によって推測される実装の詳細?

なんで?含める必要があるのはヘッダーだけuseful.hです。実装は非表示にする必要があります (そしてuseful.cpp、おそらく動的ライブラリ形式に存在します)。

宣言useful.hを行うことで、必要なクラスのみを選択して含めることができます。using useful::Useful

于 2010-02-04T19:43:29.717 に答える
2

DavidRodríguezの2番目の段落を拡張したいと思います-dribeasの回答(賛成):

転送を簡単にするために、ライブラリインターフェイスを転送するだけの(またはクラスや必要なものを実装する)役に立つfwd.hのような転送ヘッダー(STLにはいくつかありますが、ライブラリで見つけることができます)を書くことができます。しかし、これはカップリングとは何の関係もありません。

これはあなたの問題の核心を示していると思います。名前空間はここでは赤いニシンです。構文上の依存関係を含める必要性を過小評価することに悩まされました。

私はあなたの「怠惰」を理解できます:オーバーエンジニアリング(エンタープライズHelloWorld.java)は正しくありませんが、最初にコードを目立たなくして(必ずしも間違っているとは限りません)、コードが成功した場合、成功は引きずりますそのリーグの上にそれ。秘訣は、前方互換性のある方法でかゆみを掻くテクニックに切り替える(または必要が現れた最初の瞬間から採用する)適切な瞬間を感知することです。

プロジェクトに対するきらめく前方宣言は、2回目以降のラウンドを懇願しているだけです。「標準ストリームを前方宣言せず、代わりに使用する」というアドバイスを読むのに、実際にはC ++プログラマーである必要はありません<iosfwd>(これが関係するのは数年前ですが、1999年?VC6の時代です)。少し立ち止まると、アドバイスに耳を貸さなかったプログラマーからの痛ましい叫び声がたくさん聞こえます。

私はそれを低額に保ちたいという衝動を理解することができ#include <usefulfwd.h>ますが、それはそれ以上の苦痛ではなくclass Useful規模が大きいことを認めなければなりません。この単純な委任だけで、からへN-1の変更を回避できます。class Usefulclass useful::Useful

もちろん、クライアントコードでのすべての使用に役立つわけではありません。簡単なヘルプ:実際、大規模なアプリケーションでライブラリを使用する場合は、ライブラリに付属のフォワードヘッダーをアプリケーション固有のヘッダーでラップする必要があります。これの重要性は、依存関係の範囲とライブラリの変動性とともに増大します。

src / libuseful / usefulfwd.h

#ifndef GUARD
#define GUARD
namespace useful {
    class Useful;
} // namespace useful
#endif

src / myapp / myapp-usefulfwd.h

#ifndef GUARD
#define GUARD
#include <usefulfwd.h>
using useful::Useful;
#endif

基本的に、それはコードをDRYに保つことの問題です。キャッチーなTLAは気に入らないかもしれませんが、これは真にコアなプログラミング原理を説明しています。

于 2010-02-04T22:13:34.677 に答える
0

実のところ、C++でのコードの絡み合いを簡単に回避する方法はありません。ただし、グローバル名前空間を使用するのは最悪のアイデアです。実装を選択する方法がないためです。あなたはオブジェクトの代わりにオブジェクトで終わります。ソースを編集しながら編集できるので、これは社内で問題なく機能しますが、誰かがそのようなコードを顧客に出荷する場合、彼らはそれらが長く続くことを期待するべきではありません。

usingステートメントを使用すると、グローバルになっている場合もありますが、cppファイルで使用すると便利な場合があります。したがって、名前空間にはすべてが含まれている必要がありますが、社内ではすべて同じ名前空間である必要があります。そうすれば、他の人が災害なしであなたのコードを使用することができます。

于 2010-02-04T20:22:11.420 に答える
0

「有用な」ライブラリの複数の実装がある場合、グローバルな名前空間であろうと有用な名前空間であろうと、それらが同じ名前空間を使用する可能性は同じではありませんか?

別の言い方をすれば、グローバル名前空間に対して名前付き名前空間を使用することは、ライブラリ/実装にどのように「結合」しているかとは何の関係もありません。

首尾一貫したライブラリの進化戦略では、API に対して同じ名前空間を維持する必要があります。実装は、ユーザーから隠されているさまざまな名前空間を利用する可能性があり、これらはさまざまな実装で変更される可能性があります。これが「名前空間によって推測される実装の詳細」という意味かどうかはわかりません。

于 2010-02-04T19:49:21.967 に答える