C++ Move-Assignment

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Antworten
Benutzeravatar
Jonathan
Establishment
Beiträge: 2367
Registriert: 04.08.2004, 20:06
Kontaktdaten:

C++ Move-Assignment

Beitrag von Jonathan »

Ich habe gerade einen Compilerfehler, der sich trivial anfühlt, aber ich komme gerade nicht auf die Lösung:

Code: Alles auswählen

class SoundController
{
private:
	SoundController(SoundFile& file, SoLoud::Soloud& engine, bool start_playing, bool play_3d); //only called by sound file
public:
	SoundController() = delete;
	SoundController(SoundController&) = delete;
	SoundController& operator=(SoundController&) = delete;

	SoundController(SoundController&&) = default;
	SoundController& operator=(SoundController&&) = default;

	~SoundController();

Code: Alles auswählen

SoundController controller =sound1.PlayControlled(false, true);
SoundController s = sound2.PlayControlled(false, true);
controller = std::move(s);
Ok, SoundController Objekte werden von SoundFiles (sound1, sound2) erzeugt, kein Problem. Bei der Neuzuweisung bekomme ich allerdings folgende Fehlermeldung:
error C2280: 'SoundController &SoundController::operator =(SoundController &)': attempting to reference a deleted function
Das irritiert mich, denn std::move sollte eigentlich ein SoundController&& zurück geben und damit den move-assignment-operator (der auf default gesetzt ist) aufrufen. Direktes kopieren will ich vermeiden, da jeder Controller genau einen Sound managed und auch wieder freigibt. Wo liegt da jetzt der Fehler?

Compiler ist VS 2015 (14.0.25431.01 Update 3).
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
NytroX
Establishment
Beiträge: 363
Registriert: 03.10.2003, 12:47

Re: C++ Move-Assignment

Beitrag von NytroX »

Ich würde sagen dein Compiler ist zu alt, oder du hast die C++ Version nicht mit angegeben.
Habs mal unter Godbolt ausprobiert, da scheint es zu funktionieren (MSVC 19.irgendwas und auch Clang/GCC):
https://godbolt.org/z/T35Wz6
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: C++ Move-Assignment

Beitrag von Spiele Programmierer »

Ich glaube eigentlich eher nicht, dass es am Compiler liegt. Move-Semantik wurden schon vorher von VS unterstützt (2010? 2012?) und auf MSVC waren früher generell nie irgendwelche Einstellungen notwendig, um neue C++-Features zu verwenden.

Nitrox hat aber damit recht, dass der Code kompiliert. Dann müsste der Fehler also in etwas stecken, was du nicht gezeigt hast.

Meine Ideen wären:
- Evt. gibt "PlayControlled" eine Referenz zurück und es wird deswegen der Kopierkonstruktor aufgerufen?
- Warum nimmt der Kopierkonstruktor eigentlich eine Referenz, die nicht konstant ist? Ist das Absicht?

EDIT:
Man kann auf Godbolt übrigens auf VS 2015 umstellen. Es kompiliert trotzdem alles.

EDIT 2:
Noch ein Gedanke: Vlt. hast du ein Makro das "controller" oder "s" heißt?
Benutzeravatar
Jonathan
Establishment
Beiträge: 2367
Registriert: 04.08.2004, 20:06
Kontaktdaten:

Re: C++ Move-Assignment

Beitrag von Jonathan »

Es lag anscheinend daran, dass der SoundController Referenzen auf file und engine bekommt und diese dann in Membervariablen (ebenfalls Referenzen) speichert. Macht auch irgendwie Sinn, durch den Assignmentoperator müssten die Referenzen ja dann auch auf andere Objekte zeigen, aber da man Referenzen nicht nachträglich ändern kann, könnte man bloß das Originalobjekt mit einer Kopie des neuen Objekte überschreiben was falsch wäre.

Ich habe jetzt alle Referenzen durch Zeiger ersetzt und alles funktioniert. In dem Godbolt Code wird ja auch ein int statt eine Referenz als Member verwendet, das Verhalten stimmt damit also überein.

Allerdings wäre es interessant zu wissen, ob move assignment und Referenzen-Member sich tatsächlich prinzipiell gegenseitig ausschließen. Eigentlich fand ich Referenzen nämlich netter als Zeiger.
Lieber dumm fragen, als dumm bleiben!
https://jonathank.de/games/
Spiele Programmierer
Establishment
Beiträge: 426
Registriert: 23.01.2013, 15:55

Re: C++ Move-Assignment

Beitrag von Spiele Programmierer »

Aha, ja das ergibt Sinn. Darauf bin ich auch schon des öfteren reingefallen und habe viel zu lange gesucht. Ich wünschte der VS C++-Compiler würde bei expliziten default-Funktionen den Fehler direkt auch dort anzeigen, wenn die Funktion verwendet wird aber nicht generiert werden kann. Wenn man die Funktion in der Quelldatei default macht, dann geht es ja auch.

Bezüglich deiner Frage: Ja, Referenzen als Klassenvariablen sind doof. Die Funktionen können nicht generiert werden, da die Zuweisungsoperatoren ja die Variablen überschreiben würden.

Eine Möglichkeit es trotzdem zu erzwingen ist es, den Zuweisungsoperator wie folgt über den Konstruktor zu implementieren:

Code: Alles auswählen

#include <new>
SoundController& SoundController::operator=(SoundController&& other)
{
    if (this == &other)
        return *this;
    this->~SoundController();
    ::new(this) SoundController(static_cast<SoundController&&>(other));
    return *this;
}
Das habe ich in der Vergangenheit schon öfter so gemacht. Ich muss aber zugeben, dass ich mir nicht 100% sicher bin, ob das nach dem C++-Standard eigentlich überhaupt erlaubt ist. Insbesondere wenn man eben Referenzen oder andere konstante Klassenvariablen hat. Das ist auch etwas, dessen Status sich unter Umständen mal geändert hat.

Referenzen oder generell konstante Klassenvariablen sind leider generell sehr unpraktisch in C++. Wobei man konstante Klassenvariablen wenigstens schnell nicht-konstant machen kann, sollte das notwendig sein. Aber bei Referenzen hat man dann einen riesen Ärger. Und dabei ist es nur eine Frage der Zeit bis man sie doch mal neuzuweisen möchte (vlt. auch zu Test-/Debuggingzwecken). Und dann muss man ja alle Zugriffsstellen manuell mit * versehen bzw. alle . in -> umwandeln. Aus diesem Grund verwende ich seit einiger Zeit rein grundsätzlich keine Referenzen mehr als Klassenvariablen. Der Ärger ist das meiner Meinung nach einfach nach nicht Wert.

Das ganze Objektmodel und inklusive Move-Semantiks ist eh ein Fall für den Jammer-Thread. Dieses ganze Move ist einfach mal irre verwirrend und kompliziert implementiert. Als Beispiel in unserem Fall wäre es viel schöner, wenn standardmäßig Zuweisungen so wie in meinen Vorschlag implementiert wären, sodass man insbesondere auch (fast) nie mehr explizit Zuweisungsoperatoren selbst definieren muss, sondern nur noch die Verschiebe- und Kopierkonstruktoren. Sehr oft sind Zuweisungsoperatoren sonst nur eine manuelle Kopie des Codes im Destruktor & Konstruktor.
DerAlbi
Establishment
Beiträge: 269
Registriert: 20.05.2011, 05:37

Re: C++ Move-Assignment

Beitrag von DerAlbi »

https://stackoverflow.com/questions/204 ... nce-member

wenn ich das richtig überflogen habe, reden die Leute davon, sowas mit std::reference_wrapper<T> zu umschiffen.
Antworten