Angenommen, ich habe Folgendes, class X
wo ich den Zugriff auf ein internes Mitglied zurückgeben möchte:
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
Die beiden Elemente funktionieren X::Z()
und X::Z() const
haben identischen Code in geschweiften Klammern. Dies ist doppelter Code und kann Wartungsprobleme für lange Funktionen mit komplexer Logik verursachen .
Gibt es eine Möglichkeit, diese Codeduplizierung zu vermeiden?
Antworten:
Eine ausführliche Erläuterung finden Sie in der Überschrift "Vervielfältigung in
const
und Nichtmitgliedsfunktion vermeidenconst
" auf S. 32 . 23, in Punkt 3 "const
Wann immer möglich verwenden" in Effective C ++ , 3d, herausgegeben von Scott Meyers, ISBN-13: 9780321334879.Hier ist Meyers 'Lösung (vereinfacht):
Die beiden Besetzungen und der Funktionsaufruf mögen hässlich sein, aber sie sind korrekt. Meyers hat eine gründliche Erklärung warum.
quelle
get()const
etwas zurückgegeben wird, das als const-Objekt definiert wurde, sollte es überhaupt keine Nicht-const-Version von gebenget()
. Tatsächlich hat sich mein Denken darüber im Laufe der Zeit geändert: Die Vorlagenlösung ist der einzige Weg, um Duplikate zu vermeiden und eine vom Compiler überprüfte Konstantenkorrektheit zu erhalten. Daher würde ich persönlich keine mehr verwendenconst_cast
, um das Duplizieren von Code zu vermeiden. Ich würde zwischen Putten wählen den betrogenen Code in eine Funktionsvorlage oder lassen ihn betrogen.template<typename T> const T& constant(T& _) { return const_cast<const T&>(_); }
undtemplate<typename T> T& variable(const T& _) { return const_cast<T&>(_); }
. Dann können Sie tun:return variable(constant(*this).get());
Ja, es ist möglich, die Codeduplizierung zu vermeiden. Sie müssen die const-Member-Funktion verwenden, um die Logik zu haben, und die non-const-Member-Funktion muss die const-Member-Funktion aufrufen und den Rückgabewert in eine nicht-const-Referenz umwandeln (oder einen Zeiger, wenn die Funktionen einen Zeiger zurückgeben):
HINWEIS: Es ist wichtig, dass Sie die Logik NICHT in die Nicht-Konstanten-Funktion einfügen und die Konstanten-Funktion die Nicht-Konstanten-Funktion aufrufen lässt. Dies kann zu undefiniertem Verhalten führen. Der Grund ist, dass eine konstante Klasseninstanz als nicht konstante Instanz umgewandelt wird. Die Nicht-Const-Member-Funktion kann die Klasse versehentlich ändern, was nach den C ++ - Standardzuständen zu undefiniertem Verhalten führt.
quelle
C ++ 17 hat die beste Antwort auf diese Frage aktualisiert:
Dies hat die Vorteile, dass es:
volatile
werden, ist abervolatile
ein seltenes Qualifikationsspiel)Wenn Sie den vollständigen Abzugsweg gehen möchten, können Sie dies durch eine Hilfsfunktion erreichen
Jetzt können Sie nicht einmal durcheinander bringen
volatile
, und die Nutzung sieht so ausquelle
f()
zurückkehrt ,T
stattT&
.f()
RetourenT
nicht zwei Überladungen haben sollen, ist dieconst
Version allein ausreichend.shared_ptr
. Was ich also wirklich brauchte, war so etwas,as_mutable_ptr
das fast identisch mit demas_mutable
oben genannten aussieht , außer dass es a nimmt und zurückgibtshared_ptr
undstd::const_pointer_cast
stattdessen verwendetconst_cast
.T const*
wird dieseT const* const&&
eher gebunden als gebundenT const* const&
(zumindest in meinen Tests). Ich musste eine ÜberladungT const*
als Argumenttyp für Methoden hinzufügen , die einen Zeiger zurückgeben.Ich denke, die Lösung von Scott Meyers kann in C ++ 11 durch die Verwendung einer temporären Hilfsfunktion verbessert werden. Dies macht die Absicht viel offensichtlicher und kann für viele andere Getter wiederverwendet werden.
Diese Hilfsfunktion kann folgendermaßen verwendet werden.
Das erste Argument ist immer der this-Zeiger. Der zweite ist der Zeiger auf die aufzurufende Elementfunktion. Danach kann eine beliebige Anzahl zusätzlicher Argumente übergeben werden, damit diese an die Funktion weitergeleitet werden können. Dies erfordert C ++ 11 aufgrund der verschiedenen Vorlagen.
quelle
std::remove_bottom_const
mitgehen müssenstd::remove_const
.const_cast
. Sie können selbstgetElement
eine Vorlage erstellen und das Merkmal des darin enthaltenen Typs für die benötigtenmpl::conditional
Typen verwenden, z. B.iterator
s oderconstiterator
s, falls erforderlich. Das eigentliche Problem ist, wie eine const-Version einer Methode generiert wird, wenn dieser Teil der Signatur nicht als Vorlage verwendet werden kann.std::remove_const<int const&>
istint const &
(Top-Level-const
Qualifikation entfernen ), daher die GymnastikNonConst<T>
in dieser Antwort. Putativestd::remove_bottom_const
könnte dieconst
Qualifikation der untersten Ebene entfernen und genau das tun, wasNonConst<T>
hier geschieht:std::remove_bottom_const<int const&>::type
=>int&
.getElement
überlastet ist. Dann kann der Funktionszeiger nicht aufgelöst werden, ohne die Vorlagenparameter explizit anzugeben. Warum?likeConstVersion(TObj const* obj, TConstReturn (TObj::*memFun)(TArgs...) const, TArgs&&... args) { return const_cast<typename NonConst<TConstReturn>::type>((obj->*memFun)(std::forward<TArgs>(args)...)); }
Vollständig: gist.github.com/BlueSolei/bca26a8590265492e2f2760d3cefcf83Ein bisschen ausführlicher als Meyers, aber ich könnte das tun:
Die private Methode hat die unerwünschte Eigenschaft, dass sie ein nicht-const Z & für eine const-Instanz zurückgibt, weshalb sie privat ist. Private Methoden können Invarianten der externen Schnittstelle aufbrechen (in diesem Fall ist die gewünschte Invariante "ein const-Objekt kann nicht über Verweise auf Objekte geändert werden, die es hat-a").
Beachten Sie, dass die Kommentare Teil des Musters sind - die Benutzeroberfläche von _getZ gibt an, dass es niemals gültig ist, sie aufzurufen (abgesehen von den Accessoren natürlich): Es ist ohnehin kein denkbarer Vorteil, dies zu tun, da 1 Zeichen mehr eingegeben werden muss und nicht führen zu kleinerem oder schnellerem Code. Das Aufrufen der Methode entspricht dem Aufrufen eines der Accessoren mit einem const_cast, und das möchten Sie auch nicht. Wenn Sie sich Sorgen machen, Fehler offensichtlich zu machen (und das ist ein faires Ziel), nennen Sie es const_cast_getZ anstelle von _getZ.
Ich schätze übrigens Meyers Lösung. Ich habe keine philosophischen Einwände dagegen. Persönlich bevorzuge ich jedoch ein kleines Stück kontrollierter Wiederholung und eine private Methode, die nur unter bestimmten streng kontrollierten Umständen aufgerufen werden darf, gegenüber einer Methode, die wie Linienrauschen aussieht. Wählen Sie Ihr Gift und bleiben Sie dabei.
[Bearbeiten: Kevin hat zu Recht darauf hingewiesen, dass _getZ möglicherweise eine weitere Methode (z. B. generateZ) aufrufen möchte, die auf die gleiche Weise wie getZ auf const spezialisiert ist. In diesem Fall würde _getZ ein const Z & sehen und müsste es vor der Rückkehr const_cast. Das ist immer noch sicher, da der Boilerplate-Zubehör alles überwacht, aber es ist nicht besonders offensichtlich, dass es sicher ist. Wenn Sie dies tun und später generateZ ändern, um immer const zurückzugeben, müssen Sie außerdem getZ ändern, um immer const zurückzugeben, aber der Compiler sagt Ihnen nicht, dass Sie dies tun.
Dieser letztere Punkt über den Compiler gilt auch für Meyers empfohlenes Muster, der erste Punkt über einen nicht offensichtlichen const_cast jedoch nicht. Alles in allem denke ich, dass, wenn sich herausstellt, dass _getZ einen const_cast für seinen Rückgabewert benötigt, dieses Muster einen großen Teil seines Wertes gegenüber Meyers verliert. Da es auch Nachteile gegenüber Meyers hat, denke ich, dass ich in dieser Situation zu seinem wechseln würde. Das Refactoring von einem zum anderen ist einfach - es wirkt sich nicht auf anderen gültigen Code in der Klasse aus, da nur ungültiger Code und das Boilerplate _getZ aufrufen.]
quelle
something
in der_getZ()
Funktion eine Instanzvariable enthalten ist? Der Compiler (oder zumindest einige Compiler) wird sich darüber beschweren, dass da_getZ()
const ist, auch jede Instanzvariable, auf die darin verwiesen wird, const ist. Alsosomething
wäre dann const (es wäre vom Typconst Z&
) und könnte nicht konvertiert werdenZ&
. Nach meiner (zugegebenermaßen etwas begrenzten) Erfahrung ist die meiste Zeitsomething
in solchen Fällen eine Instanzvariable.const_cast
. Es war beabsichtigt , ein Platzhalter zu sein , dass der Code eine nicht-const Rückkehr aus dem const - Objekt erhalten erforderlich, nicht als Platzhalter für das, was würde in dem duplizierten Getter gewesen. "Etwas" ist also nicht nur eine Instanzvariable.Schöne Frage und nette Antworten. Ich habe eine andere Lösung, die keine Abgüsse verwendet:
Es hat jedoch die Hässlichkeit, ein statisches Element zu benötigen, und die Notwendigkeit, das zu verwenden
instance
Variable zu verwenden.Ich habe nicht alle möglichen (negativen) Auswirkungen dieser Lösung berücksichtigt. Bitte lassen Sie mich wissen, wenn überhaupt.
quelle
auto get(std::size_t i) -> auto(const), auto(&&)
. Warum '&&'? Ahh, also kann ich sagen:auto foo() -> auto(const), auto(&&) = delete;
this
Schlüsselwort enthalten. Ich schlage vor, dasstemplate< typename T > auto myfunction(T this, t args) -> decltype(ident)
dieses Schlüsselwort als implizites Objektinstanzargument erkannt wird und der Compiler erkennt, dass myfunction ein Mitglied ist oderT
.T
wird automatisch auf der Anrufseite abgeleitet, die immer der Typ der Klasse ist, jedoch mit kostenloser Lebenslaufqualifikation.const_cast
einen), dass sie zurückkehren kanniterator
undconst_iterator
.static
kann dies im Dateibereich anstelle des Klassenbereichs erfolgen. :-)Sie können dies auch mit Vorlagen lösen. Diese Lösung ist etwas hässlich (aber die Hässlichkeit ist in der CPP-Datei verborgen), bietet jedoch eine Compiler-Überprüfung der Konstanz und keine Codeduplizierung.
.h Datei:
CPP-Datei:
Der Hauptnachteil, den ich sehen kann, ist, dass Sie, da sich die gesamte komplexe Implementierung der Methode in einer globalen Funktion befindet, entweder die Mitglieder von X mit öffentlichen Methoden wie GetVector () oben erreichen müssen (von denen es immer eine geben muss) const und non-const version) oder Sie könnten diese Funktion zu einem Freund machen. Aber ich mag keine Freunde.
[Bearbeiten: Nicht benötigtes Include von cstdio entfernt, das während des Tests hinzugefügt wurde.]
quelle
const_cast
(was versehentlich verwendet werden könnte, um etwas zu verhindern, von dem eigentlich angenommen wird, dass es sich um etwas handelt, das nicht konstant ist).Wie wäre es, wenn Sie die Logik in eine private Methode verschieben und nur das Zeug "Referenz abrufen und zurückgeben" in den Gettern ausführen? Eigentlich wäre ich ziemlich verwirrt über die statischen und konstanten Casts in einer einfachen Getter-Funktion, und ich würde das bis auf äußerst seltene Umstände für hässlich halten!
quelle
Betrügt es, den Präprozessor zu benutzen?
Es ist nicht so ausgefallen wie Vorlagen oder Casts, aber es macht Ihre Absicht ("diese beiden Funktionen sollen identisch sein") ziemlich explizit.
quelle
Es ist für mich überraschend, dass es so viele verschiedene Antworten gibt, aber fast alle auf schwere Vorlagenmagie angewiesen sind. Vorlagen sind leistungsstark, aber manchmal schlagen Makros sie kurz und bündig. Maximale Vielseitigkeit wird oft durch die Kombination beider erreicht.
Ich habe ein Makro geschrieben
FROM_CONST_OVERLOAD()
das in die Nicht-Konstanten-Funktion eingefügt werden kann, um die Konstanten-Funktion aufzurufen.Anwendungsbeispiel:
Einfache und wiederverwendbare Implementierung:
Erläuterung:
Wie in vielen Antworten angegeben, lautet das typische Muster zur Vermeidung von Codeduplizierungen in einer Nicht-Const-Member-Funktion wie folgt:
Ein Großteil dieser Kesselplatte kann durch Typinferenz vermieden werden. Erstens
const_cast
kann eingekapselt werdenWithoutConst()
, wodurch der Typ des Arguments abgeleitet und das const-Qualifikationsmerkmal entfernt wird. Zweitens kann ein ähnlicher Ansatz verwendet werden, um dieWithConst()
zu qualifizierenthis
Zeiger , wodurch der Aufruf der Methode const-overloaded ermöglicht wird.Der Rest ist ein einfaches Makro, das dem Aufruf das korrekt qualifizierte Präfix voranstellt
this->
und const aus dem Ergebnis entfernt. Da der im Makro verwendete Ausdruck fast immer ein einfacher Funktionsaufruf mit 1: 1-Weiterleitungsargumenten ist, treten Nachteile von Makros wie Mehrfachauswertung nicht auf. Die Auslassungspunkte und__VA_ARGS__
könnten auch verwendet werden, sollten aber nicht benötigt werden, da Kommas (as Argumenttrennzeichen) stehen in Klammern.Dieser Ansatz hat mehrere Vorteile:
FROM_CONST_OVERLOAD( )
const_iterator
,std::shared_ptr<const T>
usw.). Überladen Sie dazu einfachWithoutConst()
die entsprechenden Typen.Einschränkungen: Diese Lösung ist für Szenarien optimiert, in denen die Nicht-Konstanten-Überladung genau das Gleiche tut wie die Konstanten-Überladung, sodass Argumente 1: 1 weitergeleitet werden können. Wenn sich Ihre Logik unterscheidet und Sie die const-Version nicht über aufrufen
this->Method(args)
, können Sie andere Ansätze in Betracht ziehen.quelle
Für diejenigen (wie ich), die
Hier ist eine andere Einstellung:
Es ist im Grunde eine Mischung aus den Antworten von @Pait, @DavidStone und @ sh1 ( EDIT : und eine Verbesserung von @cdhowie). Was der Tabelle hinzugefügt wird, ist, dass Sie nur eine zusätzliche Codezeile erhalten, die einfach die Funktion benennt (aber keine Argument- oder Rückgabetyp-Duplizierung):
Hinweis: gcc kann dies nicht vor 8.1 kompilieren, clang-5 und höher sowie MSVC-19 sind zufrieden (laut Compiler-Explorer ).
quelle
decltype()
s nicht auchstd::forward
die Argumente verwenden, um sicherzustellen, dass wir den richtigen Rückgabetyp verwenden, wenn wir Überladungen davon habenget()
, die unterschiedliche Arten von Referenzen annehmen?NON_CONST
Makro leitet den Rückgabetyp falsch ab undconst_cast
s auf den falschen Typ, da diedecltype(func(a...))
Typen nicht weitergeleitet werden. Das Ersetzen durchdecltype(func(std::forward<T>(a)...))
Löst dies . (Es gibt nur einen Linker-Fehler, da ich keine der deklariertenX::get
Überladungen definiert habe .)Hier ist eine C ++ 17-Version der statischen Hilfsfunktion der Vorlage mit und optionalem SFINAE-Test.
Vollversion: https://godbolt.org/z/mMK4r3
quelle
Ich habe mir ein Makro ausgedacht, das automatisch Paare von const / non-const-Funktionen generiert.
Siehe das Ende der Antwort für die Implementierung.
Das Argument von
MAYBE_CONST
wird dupliziert. In der ersten KopieCV
wird durch nichts ersetzt; und in der zweiten Kopie wird es durch ersetztconst
.Es gibt keine Begrenzung, wie oft
CV
im Makroargument erscheinen kann.Es gibt jedoch eine leichte Unannehmlichkeit. Wenn
CV
in Klammern angezeigt wird, muss diesem Klammerpaar Folgendes vorangestellt werdenCV_IN
:Implementierung:
Pre-C ++ 20-Implementierung, die nicht unterstützt
CV_IN
:quelle
In der Regel sind die Elementfunktionen, für die Sie konstante und nicht konstante Versionen benötigen, Getter und Setter. Meistens handelt es sich um Einzeiler, sodass die Codeduplizierung kein Problem darstellt.
quelle
Ich habe dies für einen Freund
const_cast
getan, der die Verwendung von ... zu Recht gerechtfertigt hat. Ohne es zu wissen, hätte ich wahrscheinlich so etwas getan (nicht wirklich elegant):quelle
Ich würde eine statische Funktionsvorlage für private Helfer wie folgt vorschlagen:
quelle
Dieser DDJ-Artikel zeigt eine Möglichkeit zur Verwendung der Vorlagenspezialisierung, bei der Sie const_cast nicht verwenden müssen. Für eine so einfache Funktion wird es allerdings wirklich nicht benötigt.
boost :: any_cast (an einem Punkt nicht mehr) verwendet einen const_cast aus der const-Version, der die Nicht-const-Version aufruft, um Doppelungen zu vermeiden. Sie können der Nicht-Const-Version jedoch keine konstante Semantik auferlegen, daher müssen Sie sehr vorsichtig sein vorsichtig sein.
Am Ende ist eine gewisse Codeduplizierung in Ordnung, solange sich die beiden Snippets direkt übereinander befinden.
quelle
Um die bereitgestellte Lösung jwfearn und kevin zu ergänzen, ist hier die entsprechende Lösung, wenn die Funktion shared_ptr zurückgibt:
quelle
Ich habe nicht gefunden, wonach ich gesucht habe, also habe ich ein paar meiner eigenen gerollt ...
Dieser ist ein wenig wortreich, hat aber den Vorteil, dass viele überladene Methoden mit demselben Namen (und Rückgabetyp) gleichzeitig behandelt werden:
Wenn Sie nur eine
const
Methode pro Name haben, aber dennoch viele Methoden zum Duplizieren vorhanden sind, bevorzugen Sie möglicherweise Folgendes:Leider bricht dies zusammen, sobald Sie anfangen, den Namen zu überladen (die Argumentliste des Funktionszeigerarguments scheint zu diesem Zeitpunkt ungelöst zu sein, sodass keine Übereinstimmung für das Funktionsargument gefunden werden kann). Obwohl Sie auch einen Ausweg daraus finden können:
Referenzargumente für die
const
Methode stimmen jedoch nicht mit den scheinbar by-value-Argumenten für die Vorlage überein, und sie wird unterbrochen.Nicht sicher warum.Hier ist warum .quelle