Gibt es für eine Klasse Foo eine Möglichkeit, das Konstruieren zu verbieten, ohne ihr einen Namen zu geben?
Beispielsweise:
Foo("hi");
Und erlauben Sie es nur, wenn Sie ihm einen Namen geben, wie den folgenden?
Foo my_foo("hi");
Die Lebensdauer des ersten ist nur die Aussage, und die zweite ist der umschließende Block. In meinem Anwendungsfall Foo
wird die Zeit zwischen Konstruktor und Destruktor gemessen. Da ich mich nie auf die lokale Variable beziehe, vergesse ich oft, sie einzugeben, und ändere versehentlich die Lebensdauer. Ich möchte stattdessen einen Fehler bei der Kompilierung erhalten.
return std::string("Foo");
)Antworten:
Eine weitere makrobasierte Lösung:
Die Aussage
Foo("hi");
erweitert sich zuclass Foo("hi");
, was schlecht geformt ist; aberFoo a("hi")
erweitert sichclass Foo a("hi")
, was richtig ist.Dies hat den Vorteil, dass es sowohl Quell- als auch Binärkompatibel mit vorhandenem (korrektem) Code ist. (Diese Behauptung ist nicht ganz richtig - siehe Johannes Schaubs Kommentar und die anschließende Diskussion unten: "Woher wissen Sie, dass die Quelle mit dem vorhandenen Code kompatibel ist? Sein Freund enthält seinen Header und hat void f () {int Foo = 0;} Die Zeile, die eine Elementfunktion der Klasse Foo definiert, schlägt fehl: void class Foo :: bar () {} " )
quelle
void f() { int Foo = 0; }
den zuvor gut kompiliert und jetzt falsch kompiliert! Außerdem schlägt jede Zeile, die eine Elementfunktion der Klasse Foo definiert, fehl :void class Foo::bar() {}
.Foo("Hi")
dh jetzt in Foo.cppclass Foo("hi");
dass das Kompilieren in Ordnung ist.Wie wäre es mit einem kleinen Hack
quelle
Foo a("hi");
(ohneclass
) wäre auch ein Fehler.Foo
da sie Vorrang vor einer Klasse hat.Foo::Foo("hi")
ist in C ++ nicht erlaubt .Machen Sie den Konstruktor privat, aber geben Sie der Klasse eine Erstellungsmethode .
quelle
Foo::create();
überFoo const & x = Foo::create();
std::common_type<Foo>::type()
und man bekommt eine vorübergehende. Oder sogartypedef Foo bar; bar()
.std::common_type<Foo>::type()
. Das versehentlich wegzulassenFoo const & x = ...
ist absolut glaubwürdig.Dieser führt nicht zu einem Compilerfehler, sondern zu einem Laufzeitfehler. Anstatt eine falsche Zeit zu messen, erhalten Sie eine Ausnahme, die möglicherweise auch akzeptabel ist.
Jeder Konstruktor, den Sie schützen möchten, benötigt ein Standardargument, für das
set(guard)
aufgerufen wird.Die Eigenschaften sind:
Der Fall
f2
,f3
und die Rückkehr der"hello"
nicht gewollt werden. Um ein Werfen zu verhindern, können Sie zulassen, dass die Quelle einer Kopie vorübergehend ist, indem Sie die Option "guard
Jetzt schützen" anstelle der Quelle der Kopie zurücksetzen . Jetzt sehen Sie auch, warum wir die obigen Zeiger verwendet haben - es ermöglicht uns, flexibel zu sein.Die Eigenschaften für
f2
,f3
undreturn "hello"
sind jetzt immer// OK
.quelle
Foo f = "hello"; // may throw
Dies ist genug, um mich zu erschrecken, diesen Code niemals zu verwenden.explicit
und dann solchen Code nicht mehr zu kompilieren. Das Ziel war es, das Temporäre zu fotografieren, und das tut es auch. Wenn Sie Angst haben, können Sie dafür sorgen, dass es nicht geworfen wird, indem Sie die Quelle einer Kopie in der Kopie festlegen oder den Konstruktor so verschieben, dass er nicht vorübergehend ist. dann darf nur das endgültige Objekt mehrerer Kopien geworfen werden, wenn es noch als temporär endet.Foo
Objekt ebenfalls temporär ist und seine Lebensdauer mit demselben Ausdruck wie das Standardargument endet, wird derFoo
dtor des Objekts vor dem dtor des Standardarguments aufgerufen, da der erstere nach dem letzteren erstellt wurde.Foo(...);
undFoo foo(...);
von innenFoo
.Vor einigen Jahren habe ich einen Patch für den GNU C ++ - Compiler geschrieben, der eine neue Warnoption für diese Situation hinzufügt. Dies wird in einem Bugzilla-Artikel verfolgt .
Leider ist GCC Bugzilla eine Grabstätte, auf der wohlüberlegte Vorschläge für Patch-Features sterben. :) :)
Dies wurde durch den Wunsch motiviert, genau die Art von Fehlern zu erkennen, die Gegenstand dieser Frage im Code sind, der lokale Objekte als Gadgets zum Sperren und Entsperren, Messen der Ausführungszeit usw. verwendet.
quelle
Bei Ihrer Implementierung können Sie dies nicht tun, aber Sie können diese Regel zu Ihrem Vorteil verwenden:
Sie können den Code aus der Klasse in eine freistehende Funktion verschieben, die einen nicht konstanten Referenzparameter verwendet. In diesem Fall wird ein Compilerfehler angezeigt, wenn ein temporärer Benutzer versucht, eine Bindung an die nicht konstante Referenz herzustellen.
Codebeispiel
Ausgabe
quelle
x
ist ein benanntes Objekt, daher ist nicht klar, ob wir es wirklich verbieten wollen. Wenn der Konstruktor, den Sie verwendet hätten, explizit ist, könnten die Leute dies instinktiv tunFoo f = Foo("hello");
. Ich denke, sie würden wütend werden, wenn es fehlschlagen würde. Meine Lösung lehnte es zunächst (und in sehr ähnlichen Fällen) mit einer Ausnahme / einem Assert-Fehler ab, und jemand beschwerte sich.Sie haben einfach keinen Standardkonstruktor und benötigen in jedem Konstruktor einen Verweis auf eine Instanz.
quelle
S(selfRef, a);
. : /S(SelfRef, S const& s) { assert(&s == this); }
, wenn ein Laufzeitfehler akzeptabel ist.Nein, ich fürchte, das ist nicht möglich. Sie können jedoch den gleichen Effekt erzielen, indem Sie ein Makro erstellen.
Wenn dies vorhanden ist, können Sie einfach FOO (x) anstelle von Foo my_foo (x) schreiben.
quelle
Foo();
.class Do_not_use_this_class_directly_Only_use_it_via_the_FOO_macro;
Beachten Sie Folgendes, da das Hauptziel darin besteht, Fehler zu vermeiden:
Auf diese Weise können Sie nicht vergessen, die Variable zu benennen, und Sie können nicht vergessen, zu schreiben
struct
. Ausführlich, aber sicher.quelle
Deklarieren Sie einen einparametrischen Konstruktor als explizit, und niemand wird jemals ungewollt ein Objekt dieser Klasse erstellen.
Beispielsweise
kann nur so verwendet werden
aber niemals so (durch eine temporäre auf dem Stapel)
Siehe auch: Alexandrescu, C ++ Coding Standards, Punkt 40.
quelle
fun(Foo("text"));
.