2.8-名前の衝突と名前空間の紹介

あなたが初めて友人の家に運転していて、あなたに与えられた住所がMill Cityの245Front Streetであるとしましょう。 ミルシティに到達すると、あなただけのミルシティは、実際にお互いから町全体に二つの異なるフロントストリートを持っていることを発見するた あなたはどちらに行くのですか? あなたが決定するのに役立ついくつかの追加の手がかりがなかった場合を除き(例えば あなたは彼の家が川の近くにあることを覚えています)あなたはあなたの友人に電話して、より多くの情報を求めなければならないでしょう。 これは混乱し、非効率的であるため(特に郵便配達員にとって)、ほとんどの国では、都市内のすべての通りの名前と家の住所は一意である必要があります。

同様に、C++ではすべての識別子があいまいでないことが必要です。 コンパイラまたはリンカがそれらを区別できないように、同じプログラムに2つの同一の識別子が導入された場合、コンパイラまたはリンカはエラーを生成します。 このエラーは、一般的に名前付けの競合(または名前付けの競合)と呼ばれます。

命名衝突の例

a.cpp:

1
2
3
4
5
6

#include<iostream>
void myFcn(int x))
{
std::cout<<x;
}

メイン。cpp:

1
2
3
4
5
6
7
8
9
10
11

#include<iostream>
void myFcn(int x))
{
std::cout<<2*x;
}
intメイン()
{
戻る0;
}

コンパイラがこのプログラムをコンパイルすると、コンパイルされますa.cpp とメイン。cppは独立して、各ファイルは問題なくコンパイルされます。

しかし、リンカが実行されると、リンカはすべての定義をリンクします。a.cpp とメイン。cppと一緒に、関数myFcnの競合する定義を発見します。 その後、リンカはエラーで中止します。 MyFcnが呼び出されない場合でも、このエラーが発生することに注意してください。

ほとんどの名前付けの衝突は2つのケースで発生します。
1)関数(またはグローバル変数)の2つ(またはそれ以上)の定義が、同じプログラムにコンパイルされた別々のファイルに導入されます。 これにより、上記のようにリンカーエラーが発生します。
2)関数(またはグローバル変数)の2つ(またはそれ以上)の定義が同じファイルに導入されます(多くの場合、#includeを介して)。 これにより、コンパイラエラーが発生します。

プログラムが大きくなり、より多くの識別子を使用するにつれて、命名の衝突が導入される確率は大幅に増加します。 良いニュースは、c++が命名の衝突を回避するための多くのメカニズムを提供することです。 関数内で定義されたローカル変数が互いに競合しないようにするLocal scopeは、そのようなメカニズムの1つです。 しかし、ローカルスコープは関数名には機能しません。 では、関数名が互いに競合しないようにするにはどうすればよいですか?

名前空間とは何ですか?

私たちの住所のアナロジーに戻る一瞬のために、それらの通りが同じ都市内に存在していたので、二つの正面通りを持つことは問題に過ぎませんでした。 一方、Mill Cityの209Front StreetとJonesvilleの417Front Streetの2つのアドレスにメールを配信する必要がある場合は、どこに行くかについて混乱することはありません。 別の言い方をすれば、都市は互いに競合する可能性のある住所を明確にするためのグループを提供します。 名前空間は、この類推で都市のように機能します。

名前空間は、曖昧さ回避のために名前をその中で宣言できる領域です。 名前空間は、その中で宣言された名前にスコープ(namespace scopeと呼ばれる)を提供します-これは単に名前空間内で宣言された名前が他のスコープ内の同一の名前と

キーインサイト

名前空間で宣言された名前は、別のスコープで宣言された同一の名前と誤解されることはありません。

名前空間内では、すべての名前が一意である必要があります。

名前空間は、他の識別子と誤って衝突しないようにするために、大規模なプロジェクトで関連する識別子をグループ化するためによく使用されます。 たとえば、すべてのmath関数をmathという名前空間に配置すると、math関数はmath名前空間外の同じ名前の関数と衝突しません。

今後のレッスンでは、独自の名前空間を作成する方法について説明します。

グローバル名前空間

C++では、クラス、関数、または名前空間内で定義されていない名前は、グローバル名前空間(グローバルスコープとも呼ばれる)と呼ばれる暗黙的に定義された名前空間の一部であるとみなされます。

レッスンの一番上の例では、関数main()とmyFcn()の両方のバージョンがグローバル名前空間内で定義されています。 この例で発生した命名の衝突は、両方のバージョンのmyFcn()がグローバル名前空間内で終了し、名前空間内のすべての名前が一意でなければならないという規則に違反するために発生します。

std名前空間

C++が最初に設計されたとき、C++標準ライブラリのすべての識別子(std::cinとstd::coutを含む)は、std::接頭辞なしで使用できました(これらはグロー ただし、これは、標準ライブラリの識別子が、独自の識別子(グローバル名前空間でも定義されている)で選択した名前と競合する可能性があることを意 標準ライブラリから新しいファイルを#includeしたときに、動作していたコードに突然名前の競合が発生する可能性があります。 さらに悪いことに、あるバージョンのC++でコンパイルするプログラムは、標準ライブラリに導入された新しい識別子が既に記述されているコードとの命名競合を持つ可能性があるため、将来のバージョンのC++でコンパイルされない可能性があります。 そのため、C++は標準ライブラリのすべての機能を”std”(標準の略)という名前の名前空間に移動しました。Std::coutの名前は実際にはstd::coutではないことが判明しました。 これは実際には単なるcoutであり、stdは識別子coutが一部である名前空間の名前です。 Coutはstd名前空間で定義されているため、coutという名前はグローバル名前空間で作成するcoutという名前のオブジェクトや関数と競合しません。同様に、名前空間で定義されている識別子(例えばstd::cout)にアクセスするときは、名前空間(std)内で定義されている識別子を探していることをコンパイ

Key insight

名前空間内で定義されている識別子(std名前空間など)を使用する場合、識別子が名前空間内にあることをコンパイラに伝える必要があります。

これを行うにはいくつかの異なる方法があります。

明示的な名前空間修飾子std::

std名前空間からcoutを使用することをコンパイラに伝える最も簡単な方法は、明示的にstd::接頭辞を使用することです。 例えば、:

1
2
3
4
5
6
7

#include<iostream>
int main()
{
std::cout<<“こんにちは。”; / coutと言うとき、std名前空間
returnで定義されたcoutを意味します0;
}

::シンボルは、スコープ解決演算子と呼ばれる演算子です。 ::シンボルの左側にある識別子は、::シンボルの右側にある名前が含まれている名前空間を識別します。 ::記号の左側に識別子が指定されていない場合は、グローバル名前空間が使用されます。したがって、std::coutと言うとき、「名前空間stdに存在するcout」と言っています。どのcoutを参照しているか(std名前空間内のもの)についてあいまいさがないため、これはcoutを使用する最も安全な方法です。

ベストプラクティス

明示的な名前空間プレフィックスを使用して、名前空間で定義された識別子にアクセスします。

Using namespace std(and why to avoid it)

名前空間内の識別子にアクセスする別の方法は、usingディレクティブステートメントを使用することです。 こちらは弊社のオリジナルの”Hello world”プログラムの使用指令:

1
2
3
4
5
6
7
8
9

#include<iostream>
using namespace std;//この使用指令語のコンパイラをチェックのnamespace stdを解決するときに識別子のない接頭辞
int main()
{
cout<<“こんにちは。”; //coutには接頭辞がないため、コンパイラはcoutがローカルまたは名前空間で定義されているかどうかを確認しますstd
return0;
}

usingディレクティブは、名前空間接頭辞を持たない識別子を解決しようとするときに、指定された名前空間をチェックするようにコンパイ したがって、上記の例では、コンパイラがcout識別子を決定すると、ローカル(未定義)とstd名前空間(std::coutと一致する場所)の両方をチェックします。

多くのテキスト、チュートリアル、さらにはいくつかのコンパイラでは、プログラムの上部にusingディレクティブを推奨または使用します。 しかし、このように使用すると、これは悪い習慣であり、非常にお勧めしません。

次のプログラムを考えてみましょう:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include<iostream>//名前空間を使用してstd::cout
の宣言をインポートしますstd;//std::coutを”cout”としてアクセス可能にします
int cout()//独自の”cout”関数を宣言します
{
リターン5;
}
int main()
{
cout<<“こんにちは、世界!”;//コンパイルエラー! ここではどのcoutが欲しいですか? Std名前空間のものか、上で定義したものですか?
戻る0;
}

コンパイラは、定義したcout関数が必要か、std名前空間内で定義されているcoutかを判断できないため、上記のプログラムはコンパイルされません。

このようにusingディレクティブを使用する場合、定義した識別子はstd名前空間内の同じ名前の識別子と競合する可能性があります。 さらに悪いことに、識別子名は今日競合しないかもしれませんが、将来の言語改訂でstd名前空間に追加された新しい識別子と競合する可能性があ これは、最初に標準ライブラリのすべての識別子をstd名前空間に移動する全体のポイントでした!

警告

プログラムの先頭にディレクティブを使用しないでください(using namespace std;など)。 彼らは名前空間が最初に追加された理由に違反します。

レッスン6.12–ステートメントの使用で、ステートメントの使用(および責任を持って使用する方法)について詳しく説明します。

2.9 — プリプロセッサの紹介

インデックス

2.7 — 複数のコードファイルを持つプログラム

コメントを残す

メールアドレスが公開されることはありません。