Wie verwendet man Namespaces in C ++ richtig?

231

Ich komme aus einem Java-Hintergrund, in dem Pakete verwendet werden, keine Namespaces. Ich bin es gewohnt, Klassen, die zusammenarbeiten, um ein vollständiges Objekt zu bilden, in Pakete zu setzen und sie später aus diesem Paket wiederzuverwenden. Aber jetzt arbeite ich in C ++.

Wie verwendet man Namespaces in C ++? Erstellen Sie einen einzelnen Namespace für die gesamte Anwendung oder erstellen Sie Namespaces für die Hauptkomponenten? Wenn ja, wie erstellen Sie Objekte aus Klassen in anderen Namespaces?

Marius
quelle

Antworten:

167

Namespaces sind im Wesentlichen Pakete. Sie können wie folgt verwendet werden:

namespace MyNamespace
{
  class MyClass
  {
  };
}

Dann im Code:

MyNamespace::MyClass* pClass = new MyNamespace::MyClass();

Wenn Sie immer einen bestimmten Namespace verwenden möchten, können Sie Folgendes tun:

using namespace MyNamespace;

MyClass* pClass = new MyClass();

Bearbeiten: Nach dem, was bernhardrusch gesagt hat, neige ich dazu, die Syntax "using namespace x" überhaupt nicht zu verwenden. Normalerweise gebe ich den Namespace explizit an, wenn ich meine Objekte instanziiere (dh das erste Beispiel, das ich gezeigt habe).

Und wie Sie unten gefragt haben , können Sie so viele Namespaces verwenden, wie Sie möchten.

Mark Ingram
quelle
25
IMO ist es besser, sich nur daran zu gewöhnen std, Symbolen den Namespace voranzustellen, als ihn usingüberhaupt zu verwenden. Also schreibe ich immer std::coutoder std::stringjetzt, weil ich sie jetzt so nenne. Ich würde niemals einfach schreiben cout.
Tom Savage
5
Dies gilt zwar sehr std, aber ich persönlich fand dies viel weniger wichtig, wenn Sie mit kleineren Bibliotheken arbeiten. Oft können Sie nur verwenden using namespace FooBario;, insbesondere wenn Sie eine beträchtliche Anzahl von Typen aus einer Bibliothek verwenden.
Jkerian
4
@jkerian, ich verstehe Ihren Standpunkt, aber ich bin anderer Meinung, weil Namenskollisionen (meiner Meinung nach) eher aus genau so kleinen Bibliotheken stammen. Die meisten Leute achten darauf, Klassen / Funktionen nicht wie in STL zu benennen. Trotzdem stimme ich zu, dass using namespace X;dies in Header-Dateien nach Möglichkeit vermieden werden sollte.
Alan Turing
12
@LexFridman "Die meisten Leute achten darauf, Klassen / Funktionen nicht wie in STL zu benennen" - das ist SO NICHT WAHR. Wenn ich zum Beispiel einen sehr speziellen E / A-Code für eine seltsame Hardware schreiben würde, würde ich niemals etwas anderes verwenden, als mylibrary::endlmeine ganz eigene Newline-Sequenz darzustellen. Ich meine, warum Namen erfinden?
Mein Compiler erkennt den Namespace immer noch nicht, obwohl ich ihn explizit angeben möchte und die Datei einbinde, in der er deklariert ist.
Bgenchel
116

Um nicht alles zu sagen, sagte Mark Ingram bereits einen kleinen Tipp für die Verwendung von Namespaces:

Vermeiden Sie die Anweisung "using namespace" in Header-Dateien. Dadurch wird der Namespace für alle Teile des Programms geöffnet, die diese Header-Datei importieren. In Implementierungsdateien (* .cpp) ist dies normalerweise kein großes Problem - obwohl ich es vorziehe, die Direktive "using namespace" auf Funktionsebene zu verwenden.

Ich denke, Namespaces werden meistens verwendet, um Namenskonflikte zu vermeiden - nicht unbedingt, um Ihre Codestruktur zu organisieren. Ich würde C ++ - Programme hauptsächlich mit Header-Dateien / der Dateistruktur organisieren.

Manchmal werden in größeren C ++ - Projekten Namespaces verwendet, um Implementierungsdetails auszublenden.

Zusätzlicher Hinweis zur using-Direktive: Einige Leute bevorzugen die Verwendung von "using" nur für einzelne Elemente:

using std::cout;  
using std::endl;
bernhardrusch
quelle
2
Ein Vorteil der "Verwendung des Namespace" auf Funktionsebene, wie Sie vorschlagen, und nicht auf der Ebene der CPP-Datei oder des Namespace {} -Blocks innerhalb der CPP-Datei, besteht darin, dass dies bei Builds mit einer einzelnen Kompilierungseinheit sehr hilfreich ist. "using namespace" ist transitiv und gilt für Namespace A über diskrete Namespace A {} -Blöcke in derselben Einheit. Bei Builds mit einer einzelnen Kompilierungseinheit verwenden Sie also schnell alles, wenn sie auf Datei- oder Namespace-Blockebene ausgeführt werden.
idij
using std::cout; ist eine Verwendung Erklärung
Konstantin
3
Ist es möglich, mehrere Namen aus einem einzigen Namespace in einer einzigen Anweisung zu verwenden? So etwas wie using std::cout, std::endl;oder sogar , using std::cout, endl;.
AlQuemist
Es kann in Ordnung sein, a using namespace xin einem Header zu verwenden, wenn es sich in einem anderen Namespace befindet. Ich würde es im Allgemeinen nicht empfehlen, aber es verschmutzt den globalen Namespace nicht.
Praxeolitic
79

Vincent Robert hat Recht in seinem Kommentar Wie verwendet man Namespaces in C ++ richtig? .

Namespace verwenden

Namespaces werden zumindest verwendet, um Namenskollisionen zu vermeiden. In Java wird dies durch die Redewendung "org.domain" erzwungen (da angenommen wird, dass man nichts anderes als seinen eigenen Domainnamen verwendet).

In C ++ können Sie dem gesamten Code in Ihrem Modul einen Namespace zuweisen. Für ein Modul MyModule.dll können Sie seinem Code beispielsweise den Namespace MyModule geben. Ich habe anderswo jemanden gesehen, der MyCompany :: MyProject :: MyModule verwendet. Ich denke, das ist übertrieben, aber alles in allem scheint es mir richtig zu sein.

Verwenden von "using"

Die Verwendung sollte mit großer Sorgfalt erfolgen, da ein (oder alle) Symbole aus einem Namespace effektiv in Ihren aktuellen Namespace importiert werden.

Es ist böse, dies in einer Header-Datei zu tun, da Ihr Header jede Quelle einschließlich dieser verschmutzt (es erinnert mich an Makros ...), und selbst in einer Quelldatei einen schlechten Stil außerhalb eines Funktionsbereichs, da er im globalen Bereich importiert wird die Symbole aus dem Namespace.

Die sicherste Möglichkeit, "using" zu verwenden, besteht darin, ausgewählte Symbole zu importieren:

void doSomething()
{
   using std::string ; // string is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   std::cout << a << std::endl ;
}

void doSomethingElse()
{
   using namespace std ; // everything from std is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   cout << a << endl ;
}

Sie werden eine Menge "using namespace std" sehen. in Tutorial- oder Beispielcodes. Der Grund ist, die Anzahl der Symbole zu reduzieren, um das Lesen zu erleichtern, nicht weil dies eine gute Idee ist.

"using namespace std;" wird von Scott Meyers entmutigt (ich erinnere mich nicht genau, welches Buch, aber ich kann es bei Bedarf finden).

Namespace-Zusammensetzung

Namespaces sind mehr als Pakete. Ein weiteres Beispiel findet sich in Bjarne Stroustrups "The C ++ Programming Language".

In der "Special Edition" unter 8.2.8 Namespace Composition beschreibt er, wie Sie zwei Namespaces AAA und BBB zu einem anderen namens CCC zusammenführen können. Somit wird CCC sowohl für AAA als auch für BBB zu einem Alias:

namespace AAA
{
   void doSomething() ;
}

namespace BBB
{
   void doSomethingElse() ;
}

namespace CCC
{
   using namespace AAA ;
   using namespace BBB ;
}

void doSomethingAgain()
{
   CCC::doSomething() ;
   CCC::doSomethingElse() ;
}

Sie können sogar ausgewählte Symbole aus verschiedenen Namespaces importieren, um Ihre eigene benutzerdefinierte Namespace-Oberfläche zu erstellen. Ich habe noch keine praktische Anwendung gefunden, aber theoretisch ist es cool.

paercebal
quelle
Könnten Sie bitte klarstellen, "geben Sie dem gesamten Code in Ihrem Modul einen Namespace"? Was ist eine gute Praxis, um in Modul zu kapseln. Zum Beispiel habe ich eine Klasse komplexer Zahlen und externe Funktionen, die sich auf komplexe Zahlen beziehen. Diese Klasse und diese beiden Funktionen sollten sich in einem Namespace befinden?
Yanpas
74

Ich habe keine Erwähnung in den anderen Antworten gesehen, also hier sind meine 2 kanadischen Cent:

Eine nützliche Anweisung zum Thema "Verwenden des Namespace" ist der Namespace-Alias, mit dem Sie einen Namespace "umbenennen" können, normalerweise um ihm einen kürzeren Namen zu geben. Zum Beispiel anstelle von:

Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::TheClassName foo;
Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::AnotherClassName bar;

Du kannst schreiben:

namespace Shorter = Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally;
Shorter::TheClassName foo;
Shorter::AnotherClassName bar;
Éric Malenfant
quelle
55

Hören Sie nicht jedem zu, der Ihnen sagt, dass Namespaces nur Namensräume sind.

Sie sind wichtig, da sie vom Compiler zur Anwendung des Schnittstellenprinzips herangezogen werden. Grundsätzlich kann dies anhand eines Beispiels erklärt werden:

namespace ns {

class A
{
};

void print(A a)
{
}

}

Wenn Sie ein A-Objekt drucken möchten, lautet der Code wie folgt:

ns::A a;
print(a);

Beachten Sie, dass wir den Namespace beim Aufrufen der Funktion nicht explizit erwähnt haben. Dies ist das Schnittstellenprinzip: C ++ betrachtet eine Funktion, die einen Typ als Argument verwendet, als Teil der Schnittstelle für diesen Typ, sodass der Namespace nicht angegeben werden muss, da der Parameter den Namespace bereits impliziert.

Warum ist dieses Prinzip wichtig? Stellen Sie sich vor, der Autor der Klasse A hat für diese Klasse keine print () -Funktion bereitgestellt. Sie müssen selbst eine bereitstellen. Da Sie ein guter Programmierer sind, definieren Sie diese Funktion in Ihrem eigenen Namespace oder möglicherweise im globalen Namespace.

namespace ns {

class A
{
};

}

void print(A a)
{
}

Und Ihr Code kann die Funktion print (a) aufrufen, wo immer Sie möchten. Stellen Sie sich nun vor, dass der Autor Jahre später beschließt, eine print () - Funktion bereitzustellen, die besser ist als Ihre, da er die Interna seiner Klasse kennt und eine bessere Version als Ihre erstellen kann.

Dann entschieden C ++ - Autoren, dass seine Version der Funktion print () anstelle der in einem anderen Namespace bereitgestellten verwendet werden sollte, um das Schnittstellenprinzip zu respektieren. Und dass dieses "Upgrade" der print () - Funktion so einfach wie möglich sein sollte, was bedeutet, dass Sie nicht jeden Aufruf der print () - Funktion ändern müssen. Aus diesem Grund können "Schnittstellenfunktionen" (Funktion im selben Namespace wie eine Klasse) aufgerufen werden, ohne den Namespace in C ++ anzugeben.

Aus diesem Grund sollten Sie einen C ++ - Namespace als "Schnittstelle" betrachten, wenn Sie einen verwenden, und das Schnittstellenprinzip berücksichtigen.

Wenn Sie dieses Verhalten besser erklären möchten, lesen Sie das Buch Exceptional C ++ von Herb Sutter

Vincent Robert
quelle
23
Sie müssen tatsächlich jeden Aufruf in print () ändern, wenn ns :: Print hinzugefügt wird, aber der Compiler markiert jeden Aufruf als mehrdeutig. Ein stiller Wechsel zur neuen Funktion wäre eine schreckliche Idee.
Eclipse
Ich frage mich jetzt, was @Vincent gesagt hat, dass Sie alle Aufrufe zum Drucken ändern müssen. Wenn der Autor die Funktion ns :: Print () bereitstellen würde, was wollten Sie sagen? Wenn der Autor eine ns :: Print () -Funktion hinzugefügt hat, können Sie einfach Ihre eigene Implementierung entfernen? Oder dass Sie nur mit ns :: print () using-Deklaration hinzufügen? Oder etwas anderes? Vielen Dank
Vaska el gato
36

Größere C ++ - Projekte, die ich gesehen habe, haben kaum mehr als einen Namespace verwendet (z. B. Boost-Bibliothek).

Tatsächlich verwendet Boost Tonnen von Namespaces. Normalerweise hat jeder Teil von Boost einen eigenen Namespace für das Innenleben und kann dann nur die öffentliche Schnittstelle in den Namespace-Boost der obersten Ebene einfügen.

Persönlich denke ich, dass je größer eine Codebasis wird, desto wichtiger werden Namespaces, selbst innerhalb einer einzelnen Anwendung (oder Bibliothek). Bei der Arbeit setzen wir jedes Modul unserer Anwendung in einen eigenen Namespace.

Eine andere Verwendung (kein Wortspiel beabsichtigt) von Namespaces, die ich häufig benutze, ist der anonyme Namespace:

namespace {
  const int CONSTANT = 42;
}

Dies ist im Grunde das gleiche wie:

static const int CONSTANT = 42;

Die Verwendung eines anonymen Namespace (anstelle eines statischen) ist jedoch die empfohlene Methode, damit Code und Daten nur in der aktuellen Kompilierungseinheit in C ++ sichtbar sind.


quelle
13
Ihre beiden Beispiele entsprechen, const int CONSTANT = 42;da die Konstante der obersten Ebene in einem Namespace-Bereich bereits eine interne Verknüpfung impliziert. In diesem Fall benötigen Sie also keinen anonymen Namespace.
Sellibitze
19

Beachten Sie außerdem, dass Sie einen Namespace hinzufügen können. Dies wird anhand eines Beispiels klarer. Ich meine, Sie können Folgendes haben:

namespace MyNamespace
{
    double square(double x) { return x * x; }
}

in einer Datei square.hund

namespace MyNamespace
{
    double cube(double x) { return x * x * x; }
}

in einer Datei cube.h. Dies definiert einen einzelnen Namespace MyNamespace( dh Sie können einen einzelnen Namespace für mehrere Dateien definieren).

OysterD
quelle
11

In Java:

package somepackage;
class SomeClass {}

In C ++:

namespace somenamespace {
    class SomeClass {}
}

Und mit ihnen Java:

import somepackage;

Und C ++:

using namespace somenamespace;

Außerdem lauten die vollständigen Namen "somepackge.SomeClass" für Java und "somenamespace :: SomeClass" für C ++. Mit diesen Konventionen können Sie organisieren, wie Sie es in Java gewohnt sind, einschließlich der Erstellung übereinstimmender Ordnernamen für Namespaces. Die Anforderungen für Ordner-> Paket- und Datei-> Klassen sind jedoch nicht vorhanden, sodass Sie Ihre Ordner und Klassen unabhängig von Paketen und Namespaces benennen können.

Staale
quelle
6

@ Marius

Ja, Sie können mehrere Namespaces gleichzeitig verwenden, z.

using namespace boost;   
using namespace std;  

shared_ptr<int> p(new int(1));   // shared_ptr belongs to boost   
cout << "cout belongs to std::" << endl;   // cout and endl are in std

[Feb. 2014 - (War es wirklich so lange her?): Dieses spezielle Beispiel ist jetzt mehrdeutig, wie Joey weiter unten ausführt. Boost und std :: haben jetzt jeweils ein shared_ptr.]

Adam Hollidge
quelle
2
Beachten Sie, dass dies inzwischen stdauch der Fall ist. Wenn Sie also versuchen, a zu verwenden, kollidieren die shared_ptrVerwendung von Namespaces boostund stdNamespaces shared_ptr.
Joey
2
Dies ist ein gutes Beispiel dafür, warum viele Softwarehäuser davon abhalten, ganze Namespaces auf diese Weise zu importieren. Es tut nicht weh, immer den Namespace anzugeben. Wenn sie zu lang sind, erstellen Sie einen Alias ​​oder nur wichtige bestimmte Klassen aus dem Namespace.
Reis
5

Sie können in einer Funktion auch "using namespace ..." enthalten, zum Beispiel:

void test(const std::string& s) {
    using namespace std;
    cout << s;
}
Shadow2531
quelle
3

Im Allgemeinen erstelle ich einen Namespace für einen Code, wenn ich glaube, dass möglicherweise Konflikte mit Funktions- oder Typnamen mit anderen Bibliotheken auftreten. Es hilft auch, Code zu brandmarken, ala boost :: .

Adam Hollidge
quelle
3

Ich bevorzuge die Verwendung eines Namespace der obersten Ebene für die Anwendung und von Sub-Namespaces für die Komponenten.

Die Art und Weise, wie Sie Klassen aus anderen Namespaces verwenden können, ist überraschenderweise der in Java sehr ähnlich. Sie können entweder "use NAMESPACE" verwenden, ähnlich einer "import PACKAGE" -Anweisung, z. B. "std". Oder Sie geben das Paket als Präfix der durch "::" getrennten Klasse an, z. B. std :: string. Dies ähnelt "java.lang.String" in Java.

dmeister
quelle
3

Beachten Sie, dass ein Namespace in C ++ wirklich nur ein Namensraum ist. Sie bieten keine der Kapselungen, die Pakete in Java ausführen, sodass Sie sie wahrscheinlich nicht so häufig verwenden.

Kristopher Johnson
quelle
2

Ich habe C ++ - Namespaces genauso verwendet wie in C #, Perl usw. Es ist nur eine semantische Trennung von Symbolen zwischen Standardbibliotheksmaterial, Material von Drittanbietern und meinem eigenen Code. Ich würde meine eigene App in einem Namespace platzieren und dann eine wiederverwendbare Bibliothekskomponente zur Trennung in einem anderen Namespace.

Spoulson
quelle
2

Ein weiterer Unterschied zwischen Java und C ++ besteht darin, dass in C ++ die Namespace-Hierarchie das Dateisystem-Layout nicht bearbeiten muss. Daher neige ich dazu, eine gesamte wiederverwendbare Bibliothek in einem einzigen Namespace und Subsysteme innerhalb der Bibliothek in Unterverzeichnissen abzulegen:

#include "lib/module1.h"
#include "lib/module2.h"

lib::class1 *v = new lib::class1();

Ich würde die Subsysteme nur in verschachtelte Namespaces stellen, wenn die Möglichkeit eines Namenskonflikts besteht.

KeithB
quelle