Ich bin gerade dabei, eine kleine Kalenderbibliothek für einen Schulauftrag zu schreiben, und ich bin auf ein unerwartetes und sehr verwirrendes Problem gestoßen. Mein Zuweisungsoperator wird nicht überschrieben, wenn ich Vorlagen einführe!
Also ist die Struktur die folgende: Ich habe eine abstrakte Klasse Date
mit meist reinen virtuellen Funktionen (einschließlich des Zuweisungsoperators), und dann habe ich zwei Unterklassen Gregorian
und Julian
, die alle ihre Funktionen implementieren. Schließlich habe ich eine Vorlage für eine Klasse Calendar
geschrieben, die das heutige Datum (in Form eines Gregorian
oder Julian
Objekts) und einige andere Dinge enthält, die für dieses spezielle Problem nicht wirklich relevant sind.
Das Problem ist, dass wenn ich versuche, dieses Mitglied today
zu setzen, bekomme ich einen langen Linker-Fehler:
Fehler 4 Fehler LNK2019: nicht aufgelöstes externes Symbol "public: virtuelle Klasse lab2 :: Datum & amp; __thiscall lab2 :: Datum :: operator = (Klasse lab2 :: Datum const & amp;)" (?? 4Date @ lab2 @@ UAEAAV01 @ ABV01 @@ Z) referenziert in der Funktion "public: Klasse lab2 :: Gregorian & amp; __thiscall lab2 :: Gregorian :: operator = (Klasse lab2 :: Gregorian const & amp;)" (?? 4Gregorian @ lab2 @@ QAEAAV01 @ ABV01 @@ Z) C: \ Benutzer ... \ test.obj Kalender
sagt mir, dass es die Funktion
Es sagt mir, dass operator=
in der Klasse Date
nicht finden kann (offensichtlich, weil es rein virtuell ist). Warum benutzt es keine der überschriebenen? Gregorian::operator=
versucht, Date::operator=
aufzurufen?
Hier ist der vereinfachte Code, bei dem Dinge schief gehen:
%Vor%Und hier ist ein Ausschnitt von Gregorian.cpp :
%Vor% Der (Default-) Konstruktor von Date setzt einfach die Werte von m_year
, m_month
und m_day
auf das heutige Datum:
Es ist jedoch erwähnenswert, dass dies vollkommen in Ordnung ist:
%Vor% So wird die Klasse Calendar
instanziiert:
Gibt es hier einen sehr offensichtlichen Fehler, den ich mache?
Wenn Sie die Fehlermeldung sorgfältig lesen, werden Sie zwei Dinge bemerken, der Compiler versucht, eine Definition für zu finden:
%Vor%Und das Symbol wird benötigt aus der Definition von:
%Vor%* Wie ist dieser Operator überhaupt in Ihrem Programm erschienen? *
Kopierkonstruktoren und Zuweisungsoperatoren sind speziell und werden immer im Programm deklariert. Entweder geben Sie eine Deklaration oder der Compiler wird es für Sie tun. Sie haben Gregorian& Gregorian::operator=(const Date&)
bereitgestellt, aber Gregorian& Gregorian::operator=(const Gregorian&)
wird nicht aus dem Programm entfernt.
Wenn Sie versuchen, ein Gregorian
-Objekt einem anderen zuzuweisen, findet der Compiler die beiden Überladungen von Ihnen und die implizit definierte und die Überladungsauflösung wird feststellen, dass die implizit deklarierte Zuweisung eine bessere Übereinstimmung ist. Dies wird die Definition des Zuweisungsoperators ähnlich wie folgt auslösen:
Es gibt verschiedene Dinge, die Sie tun können, um dieses Problem zu lösen. Am einfachsten definieren Sie Date Date::operator=(const Date&)
in Ihrem Programm (lassen Sie es als reine virtuelle Funktion, geben Sie aber auch eine Definition an). Auf diese Weise kann der Compiler, wenn er zwei Objekte des gleichen abgeleiteten Typs trifft, seine Magie wirken und alles wird funktionieren. Sie können dies auch verwenden, um die Implementierung der Kopie der Mitglieder der Basis zu faktorisieren, indem Sie den Abgang im abgeleiteten Typ erzwingen.
Eine weitere Option, die mehr Aufwand erfordert, besteht darin, den Zuweisungsoperator für alle abgeleiteten Typen zu deklarieren und zu definieren. Es gibt mehr Code, der hier geschrieben werden muss, und der Code, der die Mitglieder von Date
behandelt, muss dupliziert werden. Wenn Sie die Basisklasse ändern, müssen Sie alle abgeleiteten Zuweisungsoperatoren aktualisieren ... Ich würde nicht gehen durch diesen Pfad.
Schließlich, und sobald der Compilerfehler behoben ist, überlegen Sie, ob Ihre Implementierung des Zuweisungsoperators für eine allgemeine Date
sinnvoll ist oder nicht (was möglicherweise ein Gregorian
Datum ist). Überlegen Sie, was passiert, wenn Sie Folgendes tun:
Beachten Sie, dass das Problem in diesem Beispiel verschwindet, wenn Sie die Definition von Date::operator=
angeben und der Compiler Gregorian::operator=(Gregorian const&)
für Sie generieren lässt.
Zuallererst: Während Sie in Ihrer Klasse Date& Date::operator=(const Date&)
die Klasse Date
haben, generiert der Compiler die implizite Funktion Gregorian& Gregorian::operator=(const Gregorian&)
für Ihre Klasse Gregorian
.
Die vom Compiler generierte Funktion verhält sich wie folgt:
%Vor%Laut Überladungsauflösungsregeln ist diese Funktion besser macth im Fall
%Vor% dann Date& Date::operator=(const Date&)
Wenn Sie delete
diese Funktion verwenden oder privat machen, wird sie immer noch deklariert und wäre besser für den Compiler geeignet, da der Compiler Zugriffsprobleme ignoriert, wenn er die Überladung wählt:
[ Hinweis: Die durch die Überladungsauflösung ausgewählte Funktion ist nicht garantiert um für den Kontext angemessen zu sein. Andere Einschränkungen, wie die Zugriff auf die Funktion, kann im aufrufenden Kontext verwendet werden schlecht geformt. -Endnote ]
( 13.3 Überladungsauflösung, C ++ 11 Standardentwurf )
Sie sollten wahrscheinlich Realisation für Date operator=
function schreiben. Sie sollten wahrscheinlich das gleiche Unterstreichungsdatenformat für alle Kalenderrealisierungen haben, dann brauchen Sie keinen virtuellen Operator =. Nun versuchen Sie eine Konvertierung durchzuführen, aber um es richtig zu machen, müssen Sie nicht nur den Typ von links operator=
-Operand, sondern auch den Typ von rechts -Operanden kennen.
Der vereinfachte Code, der Ihr Problem veranschaulicht:
%Vor% %Vor%Sie denken, Sie nennen das
%Vor%aber eigentlich heißt das, automatisch generierter Standardoperator:
%Vor%Seine Standardimplementierung ruft diesen Operator auf:
%Vor%was nicht implementiert ist - also der Fehler.
Die einfachste Lösung besteht darin, eine explizite Umsetzungszuweisung vorzunehmen:
%Vor%Ich würde vorschlagen - A Operator nicht als virtuell zu definieren - es ergibt hier keinen Sinn.
Wenn Sie es wirklich virtuell wollen, dann implementieren Sie auch seine Basis rein virtuelle Version:
%Vor% Oder - lösche den Standard operator =
von allen, die von Klasse A abgeleitet sind - was sehr unpraktisch ist ...