Richtige Methode zum Definieren von C ++ - Namespace-Methoden in der CPP-Datei

108

Wahrscheinlich ein Duplikat, aber nicht leicht zu suchen ...

Bei einem Header wie:

namespace ns1
{
 class MyClass
 {
  void method();
 };
}

Ich habe method()in der CPP-Datei verschiedene Definitionen gesehen:

Version 1:

namespace ns1
{
 void MyClass::method()
 {
  ...
 }
}

Version 2:

using namespace ns1;

void MyClass::method()
{
 ...
}

Version 3:

void ns1::MyClass::method()
{
 ...
}

Gibt es einen "richtigen" Weg, dies zu tun? Sind diese "falsch", weil sie nicht alle dasselbe bedeuten?

Mr. Boy
quelle
In den meisten Codes sehe ich normalerweise die dritte Version (aber ich weiß nicht warum: D), die zweite Version ist einfach das Gegenteil davon, warum Namespaces eingeführt werden, denke ich.
Mr. Anubis
1
Interessanterweise generieren Visual Assist-Refactoring-Tools gerne Code mit # 1 oder # 3, je nachdem, welcher Stil bereits in der Zieldatei verwendet wird.
Mr. Boy

Antworten:

51

Version 2 ist unklar und nicht leicht zu verstehen, da Sie nicht wissen, zu welchem ​​Namespace MyClassgehört, und es ist nur unlogisch (Klassenfunktion nicht im selben Namespace?)

Version 1 ist richtig, da sie zeigt, dass Sie im Namespace die Funktion definieren.

Version 3 ist auch deshalb richtig, weil Sie den ::Bereichsauflösungsoperator verwendet haben, um auf den MyClass::method ()im Namespace zu verweisen ns1. Ich bevorzuge Version 3.

Siehe Namespaces (C ++) . Dies ist der beste Weg, dies zu tun.

Gilgamesh
quelle
22
# 2 "falsch" zu nennen ist eine große Übertreibung. Nach dieser Logik sind alle Symbolnamen "falsch", da sie möglicherweise andere Symbolnamen in anderen Bereichen verbergen können.
Tenfour
Es ist unlogisch. Es wird gut kompiliert (sorry, das wurde in der Antwort falsch erklärt), aber warum sollten Sie eine Funktion außerhalb ihres Namespace definieren? Es verwirrt den Leser. Wenn viele Namespaces verwendet werden, wird nicht angezeigt, zu welchem ​​Namespace MyClass gehört. Die Versionen 1 und 3 beheben dieses Problem. Zusammenfassend ist es nicht falsch, sondern nur unklar und verwirrend.
Gilgamesh
3
Ich stimme mit @PhoenicaMacia, die mit Trick ist schrecklich und zu Verwirrung führen kann. Stellen Sie sich eine Klasse vor, die einen Operator als freie Funktion implementiert. In der Kopfzeile, die Sie namespace N {struct X { void f(); }; X operator==( X const &, X const & ); }jetzt in der cpp-Datei mit der using- Anweisung haben, können void X::f() {}Sie die X operator==(X const&, X const&)Elementfunktion als definieren. Wenn Sie jedoch definieren , definieren Sie einen anderen Operator als den in der Kopfzeile definiert (Sie müssen dort entweder 1 oder 3 für die freie Funktion verwenden).
David Rodríguez - Dribeas
1
Insbesondere bevorzuge ich 1, und das Beispiel im verlinkten Artikel löst nichts wirklich auf, das erste Beispiel verwendet 1, das zweite verwendet eine Mischung aus 1 und 3 (die Funktionen werden mit Qualifikation definiert, aber sie werden innerhalb des äußeren Namespace definiert).
David Rodríguez - Dribeas
1
Von den 3 würde ich sagen, 1) ist die beste, aber die meisten IDEs haben die ziemlich nervige Angewohnheit, alles in dieser Namespace-Deklaration einzurücken, und dies führt zu Verwirrung.
Locka
28

5 Jahre später und ich dachte, ich würde das erwähnen, was beide gut aussieht und nicht böse ist

using ns1::MyClass;

void MyClass::method()
{
  // ...
}
Puzomor Kroatien
quelle
3
Dies ist die beste Antwort. Es sieht am saubersten aus und vermeidet die Probleme mit den Versionen 1 von OP, die möglicherweise unbeabsichtigt Dinge in den Namespace bringen, und 2, die unbeabsichtigt Dinge in den globalen Raum bringen.
Ayane_m
Ja, dies ist eine großartige Kombination aus weniger Eingabe als 3, während immer noch ausdrücklich die Absicht erklärt wird.
jb
14

Ich verwende Version 4 (unten), weil sie die meisten Vorteile von Version 1 (Knappheit der resoektiven Definition) und Version 3 (maximal explizit) kombiniert. Der Hauptnachteil ist, dass die Leute nicht daran gewöhnt sind, aber da ich es als technisch überlegen gegenüber den Alternativen betrachte, macht es mir nichts aus.

Version 4: Verwenden Sie die vollständige Qualifizierung mithilfe von Namespace-Aliasnamen:

#include "my-header.hpp"
namespace OI = outer::inner;
void OI::Obj::method() {
    ...
}

In meiner Welt verwende ich häufig Namespace-Aliase, da alles explizit qualifiziert ist - es sei denn, dies kann nicht (z. B. Variablennamen) oder es ist ein bekannter Anpassungspunkt (z. B. swap () in einer Funktionsvorlage).

Dietmar Kühl
quelle
1
Während ich denke , die „es besser ist , so ist mir egal, wenn es Menschen verwirrt“ Logik ist fehlerhaft, muss ich zustimmen , dass dieses ist für verschachtelte Namespaces ein guter Ansatz.
Mr. Boy
1
+1 für die ausgezeichnete Idee "Warum-habe-ich-nicht-daran gedacht"! (Was "Leute sind nicht an [neue technisch überlegene Dinge] gewöhnt" ist, werden sie sich daran gewöhnen, wenn mehr Leute es tun.)
wjl
Nur um sicherzustellen , dass ich verstehe, sind beide outerund innerdefiniert als Namensräume in anderen Header - Dateien bereits?
DekuShrub
4

Version 3 macht die Zuordnung zwischen der Klasse und dem Namespace auf Kosten einer größeren Eingabe sehr explizit. Version 1 vermeidet dies, erfasst jedoch die Zuordnung zu einem Block. Version 2 neigt dazu, dies zu verbergen, daher würde ich dies vermeiden.

Paul Joireman
quelle
3

Ich wähle Num.3 (auch bekannt als die ausführliche Version). Es ist mehr Tippen, aber die Absicht ist genau für Sie und den Compiler. Das Problem, das Sie so wie es ist gepostet haben, ist tatsächlich einfacher als die reale Welt. In der realen Welt gibt es andere Definitionsbereiche, nicht nur Klassenmitglieder. Ihre Definitionen sind nicht nur mit Klassen sehr kompliziert - da ihr Bereich nie wieder geöffnet wird (im Gegensatz zu Namespaces, globalem Bereich usw.).

Num.1 Dies kann mit anderen Bereichen als Klassen fehlschlagen - alles, was wieder geöffnet werden kann. Sie können also mit diesem Ansatz eine neue Funktion in einem Namespace deklarieren, oder Ihre Inlines werden möglicherweise über ODR ersetzt. Sie benötigen dies für einige Definitionen (insbesondere für Vorlagenspezialisierungen).

Num.2 Dies ist sehr fragil, insbesondere in großen Codebasen. Wenn sich Header und Abhängigkeiten verschieben, kann Ihr Programm nicht kompiliert werden.

Num.3 Dies ist ideal, aber es gibt viel zu tippen - was Sie beabsichtigen, um etwas zu definieren . Dies tut genau das, und der Compiler greift ein, um sicherzustellen, dass Sie keinen Fehler gemacht haben. Eine Definition stimmt nicht mit ihrer Deklaration usw. überein.

Justin
quelle
3

Es stellt sich heraus, dass es nicht nur um "Codierungsstil" geht. Num. 2 führt zu einem Verknüpfungsfehler beim Definieren und Initialisieren einer extern deklarierten Variablen in der Header-Datei. Schauen Sie sich ein Beispiel in meiner Frage an. Definition der Konstante im Namespace in der CPP-Datei

jakumate
quelle
2

Alle Wege sind richtig und jeder hat seine Vor- und Nachteile.

In Version 1 haben Sie den Vorteil, dass Sie den Namespace nicht vor jede Funktion schreiben müssen. Der Nachteil ist, dass Sie eine langweilige Identifikation erhalten, insbesondere wenn Sie mehr als eine Ebene von Namespaces haben.

In Version 2 machen Sie Ihren Code sauberer. Wenn jedoch mehr als ein Namespace im CPP implementiert ist, kann einer direkt auf die Funktionen und Variablen des anderen zugreifen, wodurch Ihr Namespace unbrauchbar wird (für diese CPP-Datei).

In Version 3 müssen Sie mehr eingeben und Ihre Funktionslinien sind möglicherweise größer als der Bildschirm, was für Designeffekte schlecht ist.

Es gibt auch eine andere Art, wie manche Leute es benutzen. Es ähnelt der ersten Version, jedoch ohne Identifizierungsprobleme.

Es ist so:

#define OPEN_NS1 namespace ns1 { 
#define CLOSE_NS1 }

OPEN_NS1

void MyClass::method()
{
...
}

CLOSE_NS1

Es liegt an Ihnen zu entscheiden, welche für jede Situation besser ist =]

Renan Greinert
quelle
14
Ich sehe hier keinen Grund, ein Makro zu verwenden. Wenn Sie nicht einrücken möchten, rücken Sie einfach nicht ein. Die Verwendung eines Makros macht den Code nur weniger offensichtlich.
Tenfour
1
Ich denke, die letzte Version, die Sie erwähnen, ist nützlich, wenn Sie Ihren Code mit alten Compilern kompilieren möchten, die keine Namespaces unterstützen (Ja, einige Dinosaurier gibt es noch). In diesem Fall können Sie das Makro in eine #ifdefKlausel einfügen.
Luca Martini
Sie müssen sich nicht identifizieren, wenn Sie nicht möchten, aber wenn Sie keine Makros verwenden, werden einige IDEs versuchen, dies für Sie zu tun. In Visual Studio können Sie beispielsweise den gesamten Code auswählen und ALT + F8 drücken, um sich automatisch zu identifizieren. Wenn Sie die Definitionen nicht verwenden, verlieren Sie diese Funktionalität. Ich denke auch nicht, dass OPEN_ (Namespace) und CLOSE_ (Namespace) weniger offensichtlich sind, wenn Sie es in Ihrem Codierungsstandard haben. Interessant ist auch der Grund, den @LucaMartini angegeben hat.
Renan Greinert
Wenn dies generisch gemacht wurde, dh #define OPEN_NS(X)ich denke, es ist ein wenig nützlich, aber nicht wirklich ... Ich habe keine Einwände gegen Makros, aber das scheint ein bisschen OTT zu sein. Ich denke, Dietmar Kühls Ansatz ist besser für verschachtelte Namespaces.
Mr. Boy