2016-04-23 2 views
2

Situation hatWie bewegen Semantik für eine Klasse-Vorlage zu unterstützen, die einen Verweis

Ich bin eine Klassenvorlage logic entwerfen, die Move-Semantik unterstützt. logic hat einen Vorlagenparameter Visitor und ein Referenzelement, dessen Typ Visitor& ist. Das ist ein Bibliothekscode.

Benutzer erbt die Klassenvorlage logic und übergibt einen benutzerdefinierten Besucher wie my_visitor. Der benutzerdefinierte Besucher kann bewegliche Mitglieder enthalten. Zum Beispiel hat my_visitor ein Element v, dessen Typ std::vector ist.

Problem

Siehe test2(). Wenn ich my_logic verschiebe, wird my_visitor::v wie erwartet verschoben. logic<Visitor>::vis bezieht sich jedoch auf das verschobene Objekt. Gibt es eine gute Möglichkeit, auf das bewegte Objekt zu verweisen?

#include <iostream> 
#include <vector> 

// Library code 
template <typename Visitor> // Concept: Visitor should have visit() 
struct logic { 
    logic(Visitor& v):vis(v) {} 
    void execute() { 
     vis.visit(); 
    } 
    // Other APIs 

    Visitor& vis; 
    // Other member variables... 
}; 

// User code 

struct my_visitor { 
    my_visitor() { v.push_back(42); } 
    void visit() { 
     std::cout << "expected 1, actual " << v.size() << std::endl; 
    } 
    std::vector<int> v; 
}; 

// User inherits all logic's APIs 
struct my_logic : logic<my_visitor> { 
    my_logic():logic<my_visitor>(mv) {} 
    my_visitor mv; 
}; 

void test1() { 
    std::cout << "test1" << std::endl; 
    my_logic m; 
    m.execute(); 
} 

void test2() { 
    std::cout << "test2" << std::endl; 
    my_logic m1; 
    { 
     my_logic m2(std::move(m1)); // logic::vis refers to moved from my_visitor... 
     m2.execute(); 
    } 
} 


int main() { 
    test1(); 
    test2(); 
} 
+0

Sie müssen Ihren Move-Konstruktor für 'my_logic' implementieren (und wahrscheinlich Bewegungszuweisung löschen). – Jarod42

Antwort

1

Das Problem ist, dass my_logic hat sowohl ein Element (mv) und einen Verweis auf dieses Element (vis), und Sie müssen sicherstellen, dass die Referenz immer auf das gleiche Element bezieht. Mit dem vordefinierten Move-Konstruktor verweist die neue Referenz vis immer noch auf das alte-Member, das dann aus verschoben wird. Deshalb sollten Sie bei 0 am Ende:

m1.mv <-----+   m2.mv 
    ↑   | 
    |   | 
    |   | 
m1.vis  +------ m2.vis 

Eine Lösung ist, wie Jarod schlägt, Ihre eigenen Kopieren/Verschieben Bauer/Zuweisungsoperator zu schreiben, um sicherzustellen, dass m2.vis-m2.mv zeigen.

Allerdings würde ich vorschlagen, einfach die zusätzliche Referenz zu vermeiden, indem nur CRTP verwenden und Ihre Basis logic Klasse, die direkt beziehen sich auf die abgeleiteten ein:

template <class Derived> 
struct logic { 
    Derived& self() { return static_cast<Derived&>(*this); } 

    void execute() { 
     self().visit(); 
    } 
}; 

struct my_visitor : logic<my_visitor) { 
    my_visitor() { v.push_back(42); } 
    void visit() { 
     std::cout << "expected 1, actual " << v.size() << std::endl; 
    } 
    std::vector<int> v;  
}; 

Auf diese Weise gibt es nur einen Weg, um die Daten zu verweisen - so kann nichts aus der Reihe geraten.

Alternativ könnten Sie explizit delete die Kopie/Move-Konstruktoren und Zuweisungsoperatoren von logic. Dies würde erfordern, dass Sie Ihre eigenen für alle abgeleiteten Typen explizit schreiben, aber sicherstellen würden, dass Sie es richtig gemacht haben. Zum Beispiel:

logic(logic&&) = delete; 

my_logic(my_logic&& rhs) 
: logic(mv) // always refer to me! 
, mv(std::move(rhs.mv)) 
{ } 
+0

danke die Antwort. Es funktioniert perfekt! Ich habe das Konzept des Template-Parameters 'logic' geändert. VisitorHolder sollte die Funktion 'visit()' haben, die einen Besucher mit der 'visit()' -Memberfunktion zurückgibt. Ich aktualisierte meinen Code als http://melpon.org/wandbox/permlink/SvnLl0sR4JrOwMMX. Ich unterstütze auch Bewegungszuweisung ohne Referenzwrapper als http://melpon.org/wandbox/permlink/xPFwfOuVavxmhcpw. –

+0

Ich kopiere/klebte URL für die Bewegungszuweisungsversion ist falsch. Das entsprechende ist http://melpon.org/wandbox/permlink/L3I5A9zWhj2QM7UN. –

1

Verwenden std::reference_wrapper anstelle einer nativen Referenz:

std::reference_wrapper ist eine Klassenvorlage, die einen Verweis in einem kopierbar, zuweisbare Objekt umschließt. Es wird häufig als Mechanismus zum Speichern von Referenzen in Standardcontainern (wie std::vector) verwendet, die normalerweise keine Referenzen enthalten können.

Insbesondere ist ein std::reference_wrapperCopyConstructible und CopyAssignable Wrapper um einen Verweis auf Funktion vom Typ zum Objekt oder Referenz T. Instanzen von std::reference_wrapper sind Objekte (sie können kopiert oder in Containern gespeichert werden), aber sie sind implizit in T& konvertierbar, sodass sie als Argumente für die Funktionen verwendet werden können, die den zugrunde liegenden Typ als Referenz verwenden.

+0

Vielen Dank für den Kommentar. Ich habe sie ersetzt. Hier ist der ursprüngliche Code http://melpon.org/wandbox/permlink/jbp876DbGDsYg6Q1 und der ersetzte Code http://melpon.org/wandbox/permlink/x1QbEaOgDHmeaxjz Wie aktualisiert man den Referenzwrapper vis? –

+1

@TakatoshiKondo: Sieht für mich eine andere Frage aus. –

+0

Mein Problem ist, wie die Klassenvorlage 'logic' eine move-Semantik mit der Aktualisierung' vis' unterstützt, die auf Objekt verschoben wird. –

0

Sie haben Ihren eigenen Zug/Copykonstruktor

struct my_logic : logic<my_visitor> { 
    my_logic():logic<my_visitor>(mv) {} 
    my_visitor mv; 

    my_logic(const my_logic& rhs) : logic<my_visitor>(mv), mv(rhs.mv) {} 
    my_logic(my_logic&& rhs) : logic<my_visitor>(mv), mv(std::move(rhs.mv)) {} 
}; 

Demo

Und mit reference_wrapper zu schreiben, können Sie auch Zuordnung in ähnlicher Art und Weise umzusetzen.

+0

Es scheint, dass der Move-Konstruktor für 'my_logic' eine neue' Logik 'erstellt. Die Klassenschablonenlogik hat andere Mitgliedsvariablen (ich habe einen Kommentar geschrieben). Es ist Zeile 18 in Ihrer Demo. Ich möchte alle Mitgliedsvariablen verschieben. Ich habe gerade Ihren Demo-Code als http://coliru.stacked-crooked.com/a/aa6ce7e2506423eb aktualisiert. Ich habe einen Konstruktor für 'logic' hinzugefügt ' '' Logik (Logik && andere, Visitor & v): vis (v), s (std :: move (other.s)) {} '' '(Zeile 9) und Aufruf von Code my_logic (my_logic && rhs): Logik (Std :: move (rhs), mv), mv (Std :: move (rhs.mv)) {} (Zeile 49). Dann funktioniert es wie erwartet. Vielen Dank. –

+0

Ich habe festgestellt, dass ich den Zuweisungsoperator auf diese Weise nicht schreiben kann. Weil es zwei Parameter benötigt. Es scheint, dass ich eine andere Mitgliedsfunktion dafür schreiben muss ... –

Verwandte Themen