Ich habe einen Wrapper für einen alten Code.
class A{
L* impl_; // the legacy object has to be in the heap, could be also unique_ptr
A(A const&) = delete;
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
... // proper resource management here
};
In diesem Legacy-Code ist die Funktion, die ein Objekt „dupliziert“, nicht threadsicher (wenn dasselbe erste Argument aufgerufen wird), daher wird sie const
im Wrapper nicht markiert . Ich schätze folgende moderne Regeln: https://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
Dies duplicate
scheint eine gute Möglichkeit zu sein, einen Kopierkonstruktor zu implementieren, mit Ausnahme der Details, die es nicht sind const
. Deshalb kann ich das nicht direkt machen:
class A{
L* impl_; // the legacy object has to be in the heap
A(A const& other) : L{other.duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
Was ist der Ausweg aus dieser paradoxen Situation?
(Nehmen wir auch an, dass dies legacy_duplicate
nicht threadsicher ist, aber ich weiß, dass das Objekt beim Beenden im ursprünglichen Zustand bleibt. Als C-Funktion ist das Verhalten nur dokumentiert, hat aber kein Konzept der Konstanz.)
Ich kann mir viele mögliche Szenarien vorstellen:
(1) Eine Möglichkeit besteht darin, dass es überhaupt keine Möglichkeit gibt, einen Kopierkonstruktor mit der üblichen Semantik zu implementieren. (Ja, ich kann das Objekt bewegen und das ist nicht das, was ich brauche.)
(2) Andererseits ist das Kopieren eines Objekts von Natur aus nicht threadsicher in dem Sinne, dass das Kopieren eines einfachen Typs die Quelle in einem halbmodifizierten Zustand finden kann, also kann ich einfach weitermachen und dies vielleicht tun,
class A{
L* impl_;
A(A const& other) : L{const_cast<A&>(other).duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(3) oder deklarieren Sie einfach duplicate
const und lügen Sie über Thread-Sicherheit in allen Kontexten. (Schließlich kümmert sich die Legacy-Funktion nicht darum, const
sodass sich der Compiler nicht einmal beschwert.)
class A{
L* impl_;
A(A const& other) : L{other.duplicate()}{}
L* duplicate() const{L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(4) Schließlich kann ich der Logik folgen und einen Kopierkonstruktor erstellen, der ein Nicht-Konstanten- Argument verwendet.
class A{
L* impl_;
A(A const&) = delete;
A(A& other) : L{other.duplicate()}{}
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
Es stellt sich heraus, dass dies in vielen Kontexten funktioniert, da diese Objekte normalerweise nicht funktionieren const
.
Die Frage ist, ist dies eine gültige oder gemeinsame Route?
Ich kann sie nicht benennen, aber ich erwarte intuitiv viele Probleme auf dem Weg zu einem nicht konstanten Kopierkonstruktor. Wahrscheinlich wird es aufgrund dieser Subtilität nicht als Werttyp qualifiziert.
(5) Obwohl dies ein Overkill zu sein scheint und hohe Laufzeitkosten verursachen könnte, könnte ich einen Mutex hinzufügen:
class A{
L* impl_;
A(A const& other) : L{other.duplicate_locked()}{}
L* duplicate(){
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
L* duplicate_locked() const{
std::lock_guard<std::mutex> lk(mut);
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
mutable std::mutex mut;
};
Aber dazu gezwungen zu sein, sieht nach Pessimisierung aus und macht die Klasse größer. Ich bin mir nicht sicher. Ich neige derzeit zu (4) oder (5) oder einer Kombination aus beiden.
- BEARBEITEN
Andere Option:
(6) Vergessen Sie den Unsinn der doppelten Elementfunktion und rufen Sie einfach legacy_duplicate
vom Konstruktor auf und erklären Sie, dass der Kopierkonstruktor nicht threadsicher ist. (Und wenn nötig, machen Sie eine andere thread-sichere Version des Typs. A_mt
)
class A{
L* impl_;
A(A const& other){legacy_duplicate(other.impl_, &impl_);}
};
BEARBEITEN 2
Dies könnte ein gutes Modell für die Funktionsweise der Legacy-Funktion sein. Beachten Sie, dass der Aufruf durch Berühren der Eingabe in Bezug auf den durch das erste Argument dargestellten Wert nicht threadsicher ist.
void legacy_duplicate(L* in, L** out){
*out = new L{};
char tmp = in[0];
in[0] = tmp;
std::memcpy(*out, in, sizeof *in); return;
}
legacy_duplicate
nicht mit demselben ersten Argument aus zwei verschiedenen Threads aufgerufen werden.
const
wirklich bedeutet. :-) Ich würde nicht zweimal darüber nachdenken, einen const&
in meine Kopie aufzunehmen, solange ich nichts ändere other
. Ich denke immer an Thread-Sicherheit als etwas, das man zusätzlich zu dem hinzufügt, auf das über mehrere Kapseln über Kapselung zugegriffen werden muss, und ich freue mich sehr auf die Antworten.
L
der durch Erstellen einer neuenL
Instanz geändert wird ? Wenn nicht, warum glauben Sie, dass dieser Vorgang nicht threadsicher ist?