Namespace + -Funktionen im Vergleich zu statischen Methoden für eine Klasse

290

Nehmen wir an, ich habe oder werde eine Reihe verwandter Funktionen schreiben. Nehmen wir an, sie haben mit Mathematik zu tun. Organisatorisch sollte ich:

  1. Schreiben Sie diese Funktionen und fügen Sie sie in meinen MyMathNamespace ein und verweisen Sie über aufMyMath::XYZ()
  2. Erstellen Sie eine Klasse mit dem Namen MyMathund machen Sie diese Methoden statisch und verweisen Sie auf ähnlicheMyMath::XYZ()

Warum sollte ich eine über die andere wählen, um meine Software zu organisieren?

RobertL
quelle
4
Zum einen sind Namespaces eine neuere Ergänzung der Sprache im Vergleich zu Klassen und statischen Methoden, die in der Sprache ab dem Zeitpunkt "C mit Klassen" verwendet wurden. Einige Programmierer sind möglicherweise mit älteren Funktionen besser vertraut. Einige andere Programmierer verwenden möglicherweise alte Compiler. Nur meine $ .02
Rom
21
@ Rom: Sie haben Recht mit "alten Programmierern", aber falsch mit "alten Compilern". Namespaces werden seit Äonen korrekt kompiliert (ich habe mit ihnen mit Visual C ++ 6 aus dem Jahr 1998 gearbeitet!). Was das "C mit Klassen" betrifft, wurden einige Leute in diesem Forum nicht einmal geboren, als das passierte: Dies als Argument zu verwenden, um eine standardmäßige und weit verbreitete C ++ - Funktion zu vermeiden, ist ein Trugschluss. Zusammenfassend lässt sich sagen, dass nur veraltete C ++ - Compiler keine Namespaces unterstützen. Verwenden Sie dieses Argument nicht als Entschuldigung, um sie nicht zu verwenden.
Paercebal
@paercebal: Einige alte Compiler werden in der eingebetteten Welt noch verwendet. Das Nicht-Unterstützen von Namespaces ist wahrscheinlich eine der kleinsten Unannehmlichkeiten, die man beim Schreiben von Code für verschiedene kleine CPUs in Kauf nehmen muss, mit denen jeder jeden Tag interagiert: Ihre Stereoanlage, Ihre Mikrowelle, das Motorsteuergerät in Ihrem Auto, die Ampel usw. Nur um Seien Sie klar: Ich befürworte nicht, nicht überall bessere, neuere Compiler zu verwenden. Au conrare: Ich bin alle für die neuesten Sprachfunktionen (außer RTTI;)). Ich möchte nur darauf hinweisen, dass eine solche Tendenz besteht
Rom
13
@Rom: Im aktuellen Fall hat der Fragesteller die Wahl, sodass anscheinend keiner seiner Compiler einen Code mit Namespace kompilieren kann. Da es sich um eine Frage zu C ++ handelt, muss eine C ++ - Antwort gegeben werden, einschließlich der Erwähnung von Namespaces und RTTI-Lösungen für das Problem, falls erforderlich. Eine C-Antwort oder eine C-mit-Klassen-für-veraltete-Compiler-Antwort zu geben, ist nicht thematisch.
Paercebal
2
"Nur meine $ .02" - es wäre mehr wert, wenn Sie Beweise für vorhandene C ++ - Compiler geliefert hätten, die keine Namespaces unterstützen. "Einige alte Compiler werden in der eingebetteten Welt immer noch verwendet" - das ist albern; Die "eingebettete Welt" ist eine neuere Entwicklung als C ++ - Namespaces. "C mit Klassen" wurde 1979 eingeführt, lange bevor jemand C-Code in irgendetwas einbettete. "Ich weise nur darauf hin, dass eine solche Tendenz besteht" - auch wenn ja, hat das nichts mit dieser Frage zu tun.
Jim Balter

Antworten:

243

Verwenden Sie standardmäßig Namespace-Funktionen.

Klassen sollen Objekte erstellen und keine Namespaces ersetzen.

Im objektorientierten Code

Scott Meyers hat einen ganzen Artikel für sein effektives C ++ - Buch zu diesem Thema geschrieben: "Bevorzugen Sie Nicht-Mitglieder-Nicht-Freund-Funktionen gegenüber Mitgliedsfunktionen". Ich habe einen Online-Verweis auf dieses Prinzip in einem Artikel von Herb Sutter gefunden:http://www.gotw.ca/gotw/084.htm

Das Wichtigste ist: In C ++ gehören Funktionen im selben Namespace wie eine Klasse zur Schnittstelle dieser Klasse (da ADL diese Funktionen beim Auflösen von Funktionsaufrufen durchsucht).

Namespace-Funktionen haben, sofern sie nicht als "Freund" deklariert sind, keinen Zugriff auf die Interna der Klasse, während statische Methoden dies tun.

Dies bedeutet zum Beispiel, dass Sie bei der Pflege Ihrer Klasse, wenn Sie die Interna Ihrer Klasse ändern müssen, in allen Methoden, einschließlich der statischen, nach Nebenwirkungen suchen müssen.

Erweiterung I.

Hinzufügen von Code zu einer Klassenschnittstelle.

In C # können Sie einer Klasse Methoden hinzufügen, auch wenn Sie keinen Zugriff darauf haben. In C ++ ist dies jedoch unmöglich.

Aber immer noch in C ++ können Sie eine Namespace-Funktion hinzufügen, selbst zu einer Klasse, die jemand für Sie geschrieben hat.

Auf der anderen Seite ist dies beim Entwerfen Ihres Codes wichtig, da Sie Ihre Benutzer durch Platzieren Ihrer Funktionen in einem Namespace dazu berechtigen, die Benutzeroberfläche der Klasse zu erweitern / zu vervollständigen.

Erweiterung II

Als Nebeneffekt des vorherigen Punktes ist es unmöglich, statische Methoden in mehreren Headern zu deklarieren. Jede Methode muss in derselben Klasse deklariert sein.

Für Namespaces können Funktionen aus demselben Namespace in mehreren Headern deklariert werden (die fast standardmäßige Swap-Funktion ist das beste Beispiel dafür).

Erweiterung III

Die grundlegende Coolness eines Namespace besteht darin, dass Sie es in einigen Codes vermeiden können, ihn zu erwähnen, wenn Sie das Schlüsselwort "using" verwenden:

#include <string>
#include <vector>

// Etc.
{
   using namespace std ;
   // Now, everything from std is accessible without qualification
   string s ; // Ok
   vector v ; // Ok
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

Und Sie können die "Verschmutzung" sogar auf eine Klasse beschränken:

#include <string>
#include <vector>

{
   using std::string ;
   string s ; // Ok
   vector v ; // COMPILATION ERROR
}

string ss ; // COMPILATION ERROR
vector vv ; // COMPILATION ERROR

Dieses "Muster" ist für die ordnungsgemäße Verwendung der fast standardmäßigen Swap-Sprache obligatorisch.

Und dies ist mit statischen Methoden in Klassen unmöglich.

C ++ - Namespaces haben also ihre eigene Semantik.

Es geht aber noch weiter, da Sie Namespaces ähnlich wie bei der Vererbung kombinieren können.

Wenn Sie beispielsweise einen Namespace A mit einer Funktion AAA und einen Namespace B mit einer Funktion BBB haben, können Sie einen Namespace C deklarieren und AAA und BBB mit dem Schlüsselwort using in diesen Namespace bringen.

Fazit

Namespaces stehen für Namespaces. Klassen sind für Klassen.

C ++ wurde so konzipiert, dass jedes Konzept anders ist und in unterschiedlichen Fällen unterschiedlich verwendet wird, um unterschiedliche Probleme zu lösen.

Verwenden Sie keine Klassen, wenn Sie Namespaces benötigen.

Und in Ihrem Fall benötigen Sie Namespaces.

paercebal
quelle
Kann diese Antwort auch auf Threads angewendet werden, dh ist es besser, Namespaces als statische Methoden für Threads zu verwenden?
Bindestrich
3
@dashesy: Namespaces im Vergleich zu statischen Methoden haben nichts mit Threads zu tun. Namespaces sind also besser, da Namespaces fast immer besser sind als statische Methoden. Wenn eine Sache vorliegt, haben statische Methoden Zugriff auf Klassenmitgliedsvariablen, sodass sie irgendwie einen niedrigeren Kapselungswert als Namespaces haben. Das Isolieren von Daten ist bei der Thread-Ausführung noch wichtiger.
Paercebal
@ paercebal- danke, ich habe die statischen Klassenmethoden für Thread-Funktionen verwendet. Jetzt verstehe ich, dass ich Klasse als Namespace missbraucht habe. Was ist Ihrer Meinung nach der beste Ansatz, um mehrere Threads in einem Objekt zu haben? Ich habe diese Frage auch auf SO gestellt, ich schätze, wenn Sie etwas Licht ins Dunkel bringen können (hier oder in der Frage selbst)
schneidig
1
@dashesy: ​​Du fragst nach Ärger. Mit verschiedenen Threads möchten Sie die Daten isolieren, die nicht gemeinsam genutzt werden sollen. Daher ist es eine schlechte Idee, mehrere Threads mit privilegiertem Zugriff auf die privaten Daten einer Klasse zu haben. Ich würde einen Thread innerhalb einer Klasse ausblenden und sicherstellen, dass die Daten für diesen Thread von den Daten für den Hauptthread isoliert werden. Natürlich können Daten, die dann gemeinsam genutzt werden sollen, Mitglieder dieser Klasse sein, sie sollten jedoch weiterhin synchronisiert werden (Sperren, atomare Daten usw.). Ich bin mir nicht sicher, auf wie viele Bibliotheken Sie Zugriff haben, aber die Verwendung von Aufgaben / Async ist noch besser.
Paercebal
paercebals Antwort sollte die akzeptierte sein! Nur noch ein Link für den fast standardmäßigen swap () über Namespace + ADL -> stackoverflow.com/questions/6380862/…
Gob00st
54

Es gibt viele Leute, die mir nicht zustimmen würden, aber so sehe ich das:

Eine Klasse ist im Wesentlichen eine Definition einer bestimmten Art von Objekt. Statische Methoden sollten Operationen definieren, die eng mit dieser Objektdefinition verbunden sind.

Wenn Sie nur eine Gruppe verwandter Funktionen haben, die keinem zugrunde liegenden Objekt oder einer Definition einer Objektart zugeordnet sind , würde ich sagen, dass Sie nur einen Namespace verwenden. Nur für mich ist dies konzeptionell viel sinnvoller.

Fragen Sie sich in Ihrem Fall beispielsweise: "Was ist ein MyMath?" Wenn MyMathkeine Art von Objekt definiert wird, würde ich sagen: Machen Sie es nicht zu einer Klasse.

Aber wie gesagt, ich weiß, dass es viele Leute gibt, die mir (sogar vehement) nicht zustimmen würden (insbesondere Java- und C # -Entwickler).

Dan Tao
quelle
3
Sie haben eine sehr reine Perspektive. Aber praktisch kann eine Klasse mit typedef
rein
56
Das liegt daran, dass Jave und C # keine Wahl haben.
Martin York
7
@ shog9. Sie können auch Funktionen templatisieren!
Martin York
6
@Dan: Vermutlich eine, die mathematische Routinen benötigte und das "Einstecken" verschiedener Implementierungen unterstützen wollte.
Shog9
1
@Dan: "Ich denke, wenn jemand daran interessiert ist, eine Klasse als Vorlagenparameter zu verwenden, definiert diese Klasse mit ziemlicher Sicherheit ein zugrunde liegendes Objekt." Nein überhaupt nicht. Denken Sie an Eigenschaften. (Trotzdem stimme ich Ihrer Antwort voll und ganz zu.)
sbi
18
  • Wenn Sie statische Daten benötigen, verwenden Sie statische Methoden.
  • Wenn es sich um Vorlagenfunktionen handelt und Sie in der Lage sein möchten, eine Reihe von Vorlagenparametern für alle Funktionen zusammen anzugeben, verwenden Sie statische Methoden in einer Vorlagenklasse.

Verwenden Sie andernfalls Namespace-Funktionen.


Antwort auf die Kommentare: Ja, statische Methoden und statische Daten werden häufig überbeansprucht. Aus diesem Grund habe ich nur zwei verwandte Szenarien angeboten, in denen ich denke, dass sie hilfreich sein können. Wenn er im spezifischen Beispiel des OP (eine Reihe von mathematischen Routinen) die Möglichkeit haben möchte, Parameter anzugeben - beispielsweise einen Kerndatentyp und eine Ausgabegenauigkeit -, die auf alle Routinen angewendet werden, könnte er Folgendes tun:

template<typename T, int decimalPlaces>
class MyMath
{
   // routines operate on datatype T, preserving at least decimalPlaces precision
};

// math routines for manufacturing calculations
typedef MyMath<double, 4> CAMMath;
// math routines for on-screen displays
typedef MyMath<float, 2> PreviewMath;

Wenn Sie das nicht brauchen, verwenden Sie auf jeden Fall einen Namespace.

Shog9
quelle
2
Sogenannte statische Daten können Daten auf Namespace-Ebene in der Implementierungsdatei des Namespace sein. Dies reduziert die Kopplung noch mehr, da sie nicht im Header angezeigt werden müssen.
Motti
Statische Daten sind nicht besser als Namespace-Globals.
Coppro
@ Coppro. Sie sind mindestens einen Schritt in der Evolutionskette von zufälligen Globalen entfernt, da sie privat gemacht werden können (aber ansonsten zustimmen).
Martin York
@Motti: OTOH, wenn du es in der Kopfzeile haben willst (Inline- / Vorlagenfunktionen), bist du wieder hässlich.
Shog9
1
Interessantes Beispiel: Verwenden Sie eine Klasse als Abkürzung, um wiederholte templateArgumente zu vermeiden !
underscore_d
13

Sie sollten einen Namespace verwenden, da ein Namespace gegenüber einer Klasse viele Vorteile bietet:

  • Sie müssen nicht alles im selben Header definieren
  • Sie müssen nicht Ihre gesamte Implementierung im Header verfügbar machen
  • Sie können kein usingKlassenmitglied sein; Sie können usingein Namespace-Mitglied
  • Sie können nicht using class, using namespaceist aber nicht allzu oft eine gute Idee
  • Die Verwendung einer Klasse impliziert, dass ein Objekt erstellt werden muss, wenn es wirklich keines gibt

Statische Mitglieder werden meiner Meinung nach sehr, sehr überbeansprucht. Sie sind in den meisten Fällen keine wirkliche Notwendigkeit. Statische Elementfunktionen sind wahrscheinlich besser als Funktionen für den Dateibereich geeignet, und statische Datenelemente sind nur globale Objekte mit einem besseren, unverdienten Ruf.

coppro
quelle
3
"Sie müssen nicht Ihre gesamte Implementierung im Header verfügbar machen", auch nicht, wenn Sie eine Klasse verwenden.
Vanuan
Noch mehr: Wenn Sie Namespaces verwenden, können Sie nicht Ihre gesamte Implementierung im Header verfügbar machen (Sie erhalten am Ende mehrere Definitionen von Symbolen). Mit Inline-Klassenmitgliedsfunktionen können Sie dies tun.
Vanuan
1
@ Vanuan: Sie können Namespace-Implementierungen im Header verfügbar machen. Verwenden Sie einfach das inlineSchlüsselwort, um ODR zu erfüllen.
Thomas Eding
@ ThomasEding nicht brauchen! = Kann
Vanuan
3
@ Vanuan: Nur eine Sache wird vom Compiler bei der Verwendung garantiert inline, und es wird NICHT der Körper einer Funktion "inliniert". Der eigentliche (und durch den Standard garantierte) Zweck von inlinebesteht darin, mehrere Definitionen zu verhindern. Lesen Sie mehr über "One Definition Rule" für C ++. Außerdem wurde die verknüpfte SO-Frage aufgrund vorkompilierter Header-Probleme anstelle von ODR-Problemen nicht kompiliert.
Thomas Eding
3

Ich würde Namespaces bevorzugen, auf diese Weise können Sie private Daten in einem anonymen Namespace in der Implementierungsdatei haben (damit sie im Gegensatz zu privateMitgliedern überhaupt nicht im Header angezeigt werden müssen). Ein weiterer Vorteil ist, dass usingdie Clients der Methoden durch Ihren Namespace die Angabe deaktivieren könnenMyMath::

Motti
quelle
2
Sie können auch private Daten in einem anonymen Namespace in der Implementierungsdatei mit Klassen haben. Ich bin mir nicht sicher, ob ich deiner Logik folge.
Patrick Johnmeyer
0

Ein weiterer Grund für die Verwendung von class - Option zur Verwendung von Zugriffsspezifizierern. Sie können dann möglicherweise Ihre öffentliche statische Methode in kleinere private Methoden aufteilen. Öffentliche Methoden können mehrere private Methoden aufrufen.

Milind T.
quelle
6
Die Zugriffsmodifikatoren sind cool, aber selbst die meisten privateMethoden sind zugänglicher als eine Methode, deren Prototyp überhaupt nicht in einem Header veröffentlicht wird (und daher unsichtbar bleibt). Ich erwähne nicht einmal die bessere Kapselung, die anonym benannte Funktionen bieten.
Paercebal
2
Private Methoden sind meiner Meinung nach schlechter, als die Funktion selbst in der Implementierung (cpp-Datei) auszublenden und sie niemals in der Header-Datei verfügbar zu machen. Bitte erläutern Sie dies in Ihrer Antwort und warum Sie lieber private Mitglieder verwenden möchten . Bis dahin -1.
unsinnig
@nonsensickle Vielleicht meint er, dass eine Mammutfunktion mit vielen wiederholten Abschnitten sicher aufgeschlüsselt werden kann, während die beleidigenden Unterabschnitte hinter privat versteckt werden, wodurch andere daran gehindert werden, auf sie zuzugreifen, wenn sie gefährlich sind / sehr sorgfältige Verwendung benötigen.
Troyseph
1
@Troyseph trotzdem können Sie diese Informationen in einem unbenannten Namespace in der .cppDatei ausblenden, wodurch sie für diese Übersetzungseinheit privat werden, ohne dass jemand, der die Header-Datei liest, überflüssige Informationen erhält. Tatsächlich versuche ich, mich für die PIMPL-Sprache einzusetzen.
unsinnig
Sie können es nicht in eine .cppDatei einfügen, wenn Sie Vorlagen verwenden möchten.
Yay295
0

Sowohl der Namespace als auch die Klassenmethode haben ihre Verwendung. Namespace kann auf mehrere Dateien verteilt werden. Dies ist jedoch eine Schwachstelle, wenn Sie den gesamten zugehörigen Code erzwingen müssen, um in eine Datei zu gelangen. Wie oben erwähnt, können Sie mit der Klasse auch private statische Mitglieder in der Klasse erstellen. Sie können es im anonymen Namespace der Implementierungsdatei haben, es ist jedoch immer noch ein größerer Bereich als sie in der Klasse zu haben.

rocd
quelle
"[Dinge speichern] im anonymen Namespace der Implementierungsdatei [ist] ein größerer Bereich als sie in der Klasse zu haben" - nein, das ist es nicht. In Fällen, in denen kein privilegierter Zugriff auf Mitglieder erforderlich ist, sind anonym genannte Inhalte privater als private:solche. und in vielen Fällen, in denen ein privilegierter Zugriff erforderlich zu sein scheint , kann dies herausgerechnet werden. Die "privateste" Funktion ist eine, die nicht in einem Header erscheint. private:Methoden können diesen Vorteil niemals genießen.
underscore_d