Ich bemerkte also, dass es möglich ist, das Einfügen privater Funktionen in Kopfzeilen zu vermeiden, indem man Folgendes ausführt:
// In file pred_list.h:
class PredicateList
{
int somePrivateField;
friend class PredicateList_HelperFunctions;
public:
bool match();
}
// In file pred_list.cpp:
class PredicateList_HelperFunctions
{
static bool fullMatch(PredicateList& p)
{
return p.somePrivateField == 5; // or whatever
}
}
bool PredicateList::match()
{
return PredicateList_HelperFunctions::fullMatch(*this);
}
Die private Funktion wird niemals im Header deklariert, und die Konsumenten der Klasse, die den Header importieren, müssen nie wissen, dass er existiert. Dies ist erforderlich, wenn es sich bei der Hilfsfunktion um eine Vorlage handelt (alternativ wird der vollständige Code in den Header eingefügt). Auf diese Weise habe ich dies "entdeckt". Ein weiterer Vorteil ist, dass Sie nicht jede Datei neu kompilieren müssen, die den Header enthält, wenn Sie eine private Member-Funktion hinzufügen, entfernen oder ändern. Alle privaten Funktionen befinden sich in der CPP-Datei.
So...
- Ist das ein bekanntes Designmuster, für das es einen Namen gibt?
- Für mich (mit Java / C # -Hintergrund und eigenem C ++ -Lernen) scheint dies eine sehr gute Sache zu sein, da der Header eine Schnittstelle definiert, während die CPP eine Implementierung definiert (und die Kompilierungszeit verbessert wird) ein schöner Bonus). Es riecht jedoch auch, als würde es eine Sprachfunktion missbrauchen, die nicht für diesen Zweck vorgesehen ist. Also, was ist das? Ist das etwas, was Sie in einem professionellen C ++ - Projekt nicht gerne sehen würden?
- Irgendwelche Fallstricke, an die ich nicht denke?
Mir ist Pimpl bekannt, eine viel robustere Methode, um die Implementierung am Rande der Bibliothek zu verstecken. Dies ist eher für die Verwendung mit internen Klassen gedacht, bei denen Pimpl Leistungsprobleme verursachen oder nicht funktionieren würde, da die Klasse als Wert behandelt werden muss.
BEARBEITEN 2: Die ausgezeichnete Antwort von Dragon Energy unten schlug die folgende Lösung vor, bei der das friend
Schlüsselwort überhaupt nicht verwendet wird :
// In file pred_list.h:
class PredicateList
{
int somePrivateField;
class Private;
public:
bool match();
}
// In file pred_list.cpp:
class PredicateList::Private
{
public:
static bool fullMatch(PredicateList& p)
{
return p.somePrivateField == 5; // or whatever
}
}
bool PredicateList::match()
{
return PredicateList::Private::fullMatch(*this);
}
Dies vermeidet den Schockfaktor von friend
(der wie dämonisiert zu sein scheint goto
), während das gleiche Prinzip der Trennung beibehalten wird.
quelle
Antworten:
Es ist, gelinde gesagt, etwas esoterisch, da Sie bereits erkannt haben, dass ich mir einen Moment lang den Kopf kratzen könnte, als ich zum ersten Mal auf Ihren Code stoße und mich frage, was Sie tun und wo diese Hilfsklassen implementiert sind, bis ich anfange, Ihren Stil aufzunehmen / Gewohnheiten (an diesem Punkt könnte ich mich total daran gewöhnen).
Mir gefällt, dass Sie die Informationsmenge in den Überschriften reduzieren. Insbesondere in sehr großen Codebasen kann dies praktische Auswirkungen haben, um die Abhängigkeiten bei der Kompilierung und letztendlich die Erstellungszeiten zu verringern.
Wenn Sie jedoch das Gefühl haben, Implementierungsdetails auf diese Weise verbergen zu müssen, sollten Sie die Übergabe von Parametern an eigenständige Funktionen mit interner Verknüpfung in der Quelldatei bevorzugen. Normalerweise können Sie Utility-Funktionen (oder ganze Klassen) implementieren, die zum Implementieren einer bestimmten Klasse nützlich sind, ohne Zugriff auf alle Interna der Klasse zu haben. Stattdessen übergeben Sie einfach die relevanten Funktionen von der Implementierung einer Methode an die Funktion (oder den Konstruktor). Und das hat natürlich den Vorteil, die Kopplung zwischen Ihrer Klasse und den "Helfern" zu verringern. Es besteht auch die Tendenz, das zu verallgemeinern, was ansonsten "Helfer" gewesen sein könnte, wenn Sie feststellen, dass sie einem allgemeineren Zweck dienen, der auf mehr als eine Klassenimplementierung anwendbar ist.
Wenn das unhandlich wird, würde ich eine zweite, idiomatischere Lösung in Betracht ziehen, nämlich den Pimpl (ich weiß, dass Sie Probleme damit angesprochen haben, aber ich denke, Sie können eine Lösung verallgemeinern, um diese mit minimalem Aufwand zu vermeiden). Dies kann eine ganze Reihe von Informationen, die Ihre Klasse implementieren muss, einschließlich ihrer privaten Daten, aus dem Header-Großhandel entfernen. Die Leistungsprobleme der Zuhälter können weitgehend mit einem billigen Zuweiser für konstante Zeit * wie einer freien Liste gemildert werden, während die Wertsemantik beibehalten wird, ohne dass ein vollständiger benutzerdefinierter Kopier-Ctor implementiert werden muss.
Persönlich würde ich nur nach Ausschöpfung dieser Möglichkeiten so etwas in Betracht ziehen. Ich halte es für eine vernünftige Idee, wenn die Alternative eher privaten Methoden gleicht, die dem Header ausgesetzt sind, wobei möglicherweise nur die esoterische Natur das praktische Problem darstellt.
Eine Alternative
Eine Alternative, die mir gerade in den Sinn gekommen ist und weitgehend dieselben Ziele ohne Freunde erreicht, ist folgende:
Nun, das scheint ein sehr strittiger Unterschied zu sein, und ich würde es immer noch als "Helfer" bezeichnen (in einem möglicherweise abfälligen Sinn, da wir immer noch den gesamten internen Zustand der Klasse an die Funktion übergeben, ob sie alles benötigt oder nicht). außer es vermeidet den "Schock" -Faktor der Begegnung
friend
. Im Allgemeinenfriend
sieht es ein bisschen beängstigend aus, wenn häufig keine weitere Überprüfung erfolgt, da darauf hingewiesen wird, dass die Interna Ihrer Klasse an anderer Stelle zugänglich sind (was impliziert, dass sie möglicherweise nicht in der Lage ist, ihre eigenen Invarianten aufrechtzuerhalten). Mit der Art, wie Sie es benutzenfriend
, wird es ziemlich umstritten, wenn die Leute sich der Praxis seit dem bewusst sindfriend
befindet sich nur in der gleichen Quelldatei und hilft, die private Funktionalität der Klasse zu implementieren, aber das oben Gesagte bewirkt fast den gleichen Effekt, zumindest mit dem möglichen Vorteil, dass es keine Freunde mit einbezieht, die diese ganze Art vermeiden ("Oh Schieß, diese Klasse hat einen Freund. Wo sonst werden ihre Privaten abgerufen / mutiert? "). Während die unmittelbar obige Version sofort mitteilt, dass es keine Möglichkeit gibt, auf die Privaten zuzugreifen / sie zu mutieren, ohne dass dies bei der Implementierung von geschehen würdePredicateList
.Das bewegt sich vielleicht in Richtung dogmatischer Gebiete mit dieser Nuance, da jeder schnell herausfinden kann, ob man Dinge einheitlich benennt
*Helper*
und in derselben Quelldatei ablegen, die sie alle im Rahmen der privaten Implementierung einer Klasse gebündelt haben. Aber wenn wirfriend
ein bisschen wählerisch werden, kann es sein, dass der direkt darüber stehende Stil auf einen Blick nicht zu einer so ruckeligen Reaktion führt, wenn das Schlüsselwort fehlt, das ein bisschen beängstigend wirkt.Für die anderen Fragen:
Dies könnte eine Möglichkeit über API-Grenzen hinweg sein, bei der der Client eine zweite Klasse mit demselben Namen definieren und auf diese Weise ohne Verknüpfungsfehler Zugriff auf die Interna erhalten könnte. Andererseits bin ich größtenteils ein C-Programmierer, der in Grafikbereichen arbeitet, in denen Sicherheitsbedenken auf dieser Ebene des "Was-wäre-wenn" auf der Prioritätenliste sehr gering sind. Daher neige ich dazu, mit den Händen zu winken und tanzen zu gehen versuche so zu tun, als gäbe es sie nicht. :-D Wenn Sie in einem Bereich arbeiten, in dem solche Bedenken sehr ernst sind, ist dies meines Erachtens eine angemessene Überlegung.
Der obige alternative Vorschlag vermeidet auch, dieses Problem zu erleiden. Wenn Sie trotzdem weitermachen möchten
friend
, können Sie dieses Problem auch vermeiden, indem Sie den Helfer zu einer privaten verschachtelten Klasse machen.Meines Wissens nach keine. Ich bezweifle, dass es eines geben würde, da es wirklich in die Details und den Stil der Implementierung geht.
"Helfer Hölle"
Ich habe eine Bitte um weitere Klärung darüber erhalten, wie ich manchmal zusammenzucken kann, wenn ich Implementierungen mit viel "Helfer" -Code sehe, und das mag bei einigen ein wenig umstritten sein, aber es ist tatsächlich so, wie ich wirklich zusammenzucken musste, als ich einige debuggte der Implementierung einer Klasse durch meine Kollegen, nur um jede Menge "Helfer" zu finden. :-D Und ich war nicht der einzige im Team, der sich am Kopf kratzte, um herauszufinden, was all diese Helfer genau tun sollen. Ich möchte auch nicht dogmatisch abschrecken, wie "Du sollst keine Helfer benutzen", aber ich würde einen winzigen Vorschlag machen, dass es hilfreich sein könnte, darüber nachzudenken, wie man Dinge umsetzt, die nicht in der Praxis vorhanden sind.
Und ja, ich beziehe private Methoden ein. Wenn ich eine Klasse mit einer einfachen öffentlichen Schnittstelle sehe, aber mit einer endlosen Menge privater Methoden, deren Zweck wie
find_impl
oderfind_detail
oder etwas unklar istfind_helper
, dann erschaudere ich auch auf ähnliche Weise.Als Alternative schlage ich Nichtmitgliedsfunktionen mit interner Verknüpfung (deklariert
static
oder in einem anonymen Namespace) vor, um die Implementierung Ihrer Klasse mit mindestens einem allgemeineren Zweck als "einer Funktion, die die Implementierung anderer unterstützt" zu unterstützen. Und ich kann Herb Sutter aus C ++ 'Coding Standards' hier zitieren, warum dies unter allgemeinen SE-Gesichtspunkten vorzuziehen ist:Sie können auch die "Mitgliedsbeiträge" verstehen, von denen er in gewissem Maße im Hinblick auf das Grundprinzip der Einschränkung des variablen Umfangs spricht. Wenn Sie sich als extremstes Beispiel ein Gott-Objekt vorstellen, das den gesamten Code enthält, der für die Ausführung Ihres gesamten Programms erforderlich ist, dann bevorzugen Sie "Helfer" dieser Art (Funktionen, ob Mitgliederfunktionen oder Freunde), die auf alle Interna zugreifen können ( privates) einer Klasse machen diese Variablen grundsätzlich nicht weniger problematisch als globale Variablen. Sie haben alle Schwierigkeiten, den Status korrekt zu verwalten, die Sicherheit von Threads zu gewährleisten und Invarianten zu verwalten, die Sie in diesem extremsten Beispiel mit globalen Variablen erhalten würden. Und natürlich sind die meisten realen Beispiele hoffentlich nicht annähernd so extrem, aber das Verbergen von Informationen ist nur so nützlich, wie es den Umfang der Informationen einschränkt, auf die zugegriffen wird.
Jetzt gibt Sutter hier bereits eine nette Erklärung, aber ich möchte noch hinzufügen, dass die Entkopplung dazu neigt, eine psychologische Verbesserung (zumindest wenn Ihr Gehirn so funktioniert wie ich) in Bezug auf Ihre Designfunktionen herbeizuführen. Wenn Sie mit dem Entwerfen von Funktionen beginnen, die nur auf die relevanten Parameter zugreifen können, die Sie übergeben, oder wenn Sie die Instanz der Klasse als Parameter übergeben, nur auf deren öffentliche Mitglieder, tendiert sie dazu, eine Design-Denkweise zu fördern, die bevorzugt Funktionen, die einen klareren Zweck haben, zusätzlich zu der Entkopplung und der Förderung einer verbesserten Kapselung, als das, was Sie sonst möglicherweise zu entwerfen versucht wären, wenn Sie nur auf alles zugreifen könnten.
Wenn wir zu den äußersten Enden zurückkehren, verleitet eine Codebasis mit globalen Variablen Entwickler nicht gerade dazu, Funktionen auf eine Weise zu entwerfen, die klar und allgemein gehalten ist. Sehr schnell, je mehr Informationen Sie in einer Funktion abrufen können, desto mehr von uns Sterblichen sind der Versuchung ausgesetzt, sie zu entarten und ihre Klarheit zu verringern, um auf all diese zusätzlichen Informationen zugreifen zu können, anstatt spezifischere und relevantere Parameter für diese Funktion zu akzeptieren den Zugang zum Staat einzuschränken, die Anwendbarkeit zu erweitern und die Klarheit der Absichten zu verbessern. Dies gilt (wenn auch im Allgemeinen in geringerem Maße) für Mitgliederfunktionen oder Freunde.
quelle
PredicateList
, ist es häufig möglich, nur ein oder zwei Mitglieder aus der Prädikatliste an eine etwas allgemeinere Funktion zu übergeben, auf die kein Zugriff erforderlich ist Jedes private Mitglied vonPredicateList
, und dies wird oft auch dazu neigen, dieser internen Funktion einen klareren, allgemeineren Namen und Zweck zu geben sowie mehr Möglichkeiten für die "Wiederverwendung von Code im Nachhinein".