Forum: PC-Programmierung [C++] Verschiebekonstruktor


von Markus (Gast)


Angehängte Dateien:

Lesenswert?

Hallo,

ich dachte, das Thema Konstruktoren verstanden zu haben, aber dem ist 
wohl nicht so...
Gegeben ist folgender Quellcode, wobei die Konstruktoren in der Form 
auch ohne explizites Hinschreiben vom Compiler automatisch hinzugefügt 
würden:
1
#include<string>
2
#include<memory>
3
#include <iostream>
4
5
using namespace std;
6
7
class SimplerName {
8
9
public:
10
    void setName(const std::string& s) {
11
        m_name = s;
12
    }
13
14
    std::string getName() const{
15
        return m_name;
16
    }
17
// Standardkonstruktor
18
    SimplerName()
19
        : m_name() {}
20
21
// Kopierkonstruktor
22
    SimplerName(const SimplerName& n)
23
        : m_name{ n.m_name } { }
24
25
// Verschiebekonstruktor
26
   SimplerName(SimplerName&& n)
27
        : m_name{ static_cast<std::string&&>(n.m_name) } {}
28
        // es ginge auch : m_name{ std::move(n.m_name) } { }
29
30
// Kopier-Zuweisungsoperator
31
    SimplerName& operator=(const SimplerName& n) {
32
        m_name = n.m_name;
33
        return *this;
34
    }
35
// Verschiebe-Zuweisungsoperator
36
    SimplerName& operator=(SimplerName&& n) {
37
        m_name = static_cast<std::string&&>(n.m_name);
38
        return *this;
39
    }
40
// Destruktor
41
    ~SimplerName() {}
42
43
private:
44
    std::string m_name;
45
};
46
47
48
49
int main() {
50
    SimplerName n1;             // Standardkonstruktor
51
    n1.setName("Peter");
52
    SimplerName n2(n1);         // Kopierkonstruktor
53
    SimplerName n3;
54
    n3 = n2;                    // Kopier-Zuweisungsoperator
55
    cout << n1.getName() << endl;
56
    cout << n2.getName() << endl;
57
    cout << n3.getName() << endl;
58
    cout << "---------" << endl;
59
60
    n2 = move(SimplerName());   // Verschiebe-Zuweisungsoperator
61
    SimplerName n4(move(SimplerName()));    // Move-Konstruktor
62
    cout << n2.getName() << endl;
63
    cout << n4.getName() << endl;
64
}


Das ganze läuft erstmal durch. 'n2' und 'n4' besitzen keinen Namen bzw. 
das Attribut 'm_name' ist leer, da in den zugehörigen Anweisungen in den 
letzten Zeilen nichts angegeben wird.

Warum aber funktionieren die folgenden Zeilen nicht?
Ich dachte, das wäre genau der Trick an der Sache, daß temporäre 
namenlose Objekte wie "Schmidt" übergeben werden können?
1
n2 = move(SimplerName{ "Schmidt" });            // error: no matching function for call to 'SimplerName::SimplerName(<brace-enclosed initializer list>)'|
2
n2 = move(SimplerName( "Schmidt" ));        // error: no matching function for call to 'SimplerName::SimplerName(const char [8])'|
3
4
n2 = SimplerName{ "Schmidt" };            // error: no matching function for call to 'SimplerName::SimplerName(<brace-enclosed initializer list>)'|
5
n2 = SimplerName( "Schmidt" );        // error: no matching function for call to 'SimplerName::SimplerName(const char [8])'|//
6
7
SimplerName n5(move(SimplerName{ "Meier" }));
8
SimplerName n5(move(SimplerName( "Meier" )));
9
10
SimplerName n5(SimplerName{ "Meier" });
11
SimplerName n5(SimplerName( "Meier" ));
12
13
SimplerName n5{SimplerName{ "Meier" }};

In einem Beispiel vorher hat der Autor eine Klasse 'Name' implementiert, 
da funktioniert das alles. Allerdings wird hier auf das 'move' 
verzichtet, der Aufruf lautet einfach:
1
Name n7{ Name{ "Muller" } };

Wo ist hier der Unterschied bzw. was muß ich in 'SimplerName' ändern, um 
einen Namen übergeben zu können?

In der Klasse 'Name' sind die Konstruktoren selbst geschrieben. Ändere 
ich den Verschiebekonstruktor in 'SimplerName' entsprechend ab, 
funktioniert das jedoch nicht:
1
    SimplerName(SimplerName&& n) {
2
        m_name = n.m_name;
3
        n.m_name = nullptr;
4
    }
 Wieso nicht?

von Oliver S. (oliverso)


Lesenswert?

Markus schrieb:
> // error: no matching function for call to
> 'SimplerName::SimplerName(<brace-enclosed initializer list>)'|

Genau lesen, was da steht. Das Problem ist nicht das move.

Oliver

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Du hast überhaupt keinen Konstruktor welcher einen String-Parameter hat, 
wie soll SimplerName{ "Meier" } dann funktionieren?

Und warum eigentlich static_cast<std::string&&> statt std::move ?

Hier:
1
    std::string getName() const{
2
        return m_name;
3
    }

Legst du jedes Mal eine Kopie an.
1
    const std::string& getName() const{
2
        return m_name;
3
    }

wäre effizienter. Der Aufrufer kann falls nötig immer noch eine Kopie 
anlegen.

: Bearbeitet durch User
von Markus (Gast)


Lesenswert?

Niklas G. schrieb:
> Du hast überhaupt keinen Konstruktor welcher einen String-Parameter hat,
> wie soll SimplerName{ "Meier" } dann funktionieren?

Ah, wenn ich den Standardkonstruktor ändere zu
1
    SimplerName(std::string s = "Oskar") {
2
        m_name = s;
3
    }
erfolgt bei Aufruf von bspw.
1
SimplerName n5(move(SimplerName{ "Meier" }));
 eine explizite Typumwandlung (?). Jedenfalls funktionieren mit diesem 
Standardkonstruktor die oben genannten Aufrufe von n2 und n5, und zwar 
allesamt, egal ob mit oder ohne 'move'. Wozu ist das dann gut?


> Und warum eigentlich static_cast<<std::string&&>> statt std::move ?
Im Buch heißt es dazu:
(Zitat Anfang)
Der vom Compiler zu unserer Klasse hinzugefügte Verschiebekonstruktor 
sieht folgendermaßen aus:
1
SimplerName(SimplerName&& n)
2
        : m_name{ static_cast<std::string&&>(n.m_name) } {}
Der 'static cast' ist notwendig, um aus der lvalue-Referenz n.m_name 
eine rvalue-Referenz zu machen, damit für das Attribut ebenfalls der 
Verschiebekonstruktor aufgerufen wird. Hätten wir den Konstruktor selbst 
programmiert, wäre auch der Einsatz von 'move' möglich gewesen:
1
SimplerName(SimplerName&& n)
2
        : m_name{ std::move(n.m_name) } { }
(Zitat Ende)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Markus schrieb:
> , egal ob mit oder ohne 'move'. Wozu ist das dann gut?

Zu nichts. Der Ausdruck 'SimplerName{ "Schmidt" }' ist bereits ein 
rvalue. Da noch ein move() drum zu packen bringt nichts.

Markus schrieb:
> Ah, wenn ich den Standardkonstruktor ändere zu

Besser wäre:
1
SimplerName(std::string s = "Oskar") : m_name (std::move (s))
2
{}

Dann noch zusätzlich, um explizit eine Kopie anzufordern:
1
SimplerName(const std::string& s) : m_name (s)
2
{}

Vermeidet den Zwischenschritt.

Markus schrieb:
> Hätten wir den Konstruktor selbst programmiert, wäre auch der Einsatz
> von 'move' möglich gewesen:

Ja, und move ist lesbarer und kürzer, würde ich daher dem manuellen 
static_cast vorziehen.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.