2013-07-03 16 views
5

Nachdem ich den Wechsel zu C++ 11 vorgenommen habe, übertrage ich nun systematisch meine Strings nach Wert in meinen Konstruktoren. Aber jetzt merke ich, dass es macht es leichter Fehler einzuführen, wenn auch der Wert im Körper des Konstruktor:Fehler bei der Verwendung eines verschobenen Werts

class A(std::string val): 
    _val(std::move(val)) 
{ 
    std::cout << val << std::endl; // Bug!!! 
} 

Was kann ich die Chancen zu reduzieren, tun es falsch zu bekommen?

+7

"machte die _move_ bis C 11 ++" very nice –

+0

"Was kann ich die Chancen auf es falsch zu reduzieren tun?" Nicht viel. Objekte in gültigen, aber seltsamen Zuständen zu belassen, ist lächerlich, was ein Grund dafür ist, dass die Umzugs-Semantik von C++ 11 albern ist. –

+0

Was ist falsch an der Verwendung von _val im Körper des Konstruktors? – Thorsten

Antwort

2

Name Argumente, dessen Zweck es ist, in einem gewissen unverwechselbaren Art und Weise eingefahrene von werden, zumindest im Rahmen der Umsetzung des Konstrukteurs

A::A(std::string val_moved_from): 
_val(std::move(val_moved_from)) 
{ 
    std::cout << val_moved_from << std::endl; // Bug, but obvious 
} 

dann von ihnen so früh wie möglich bewegen (in der Konstruktionsliste, sagt) .

Wenn Sie solch eine lange Konstruktionsliste haben, können Sie zwei Verwendungen von val_moved_from darin vermissen, das hilft nicht.

Eine Alternative wäre, einen Vorschlag zu schreiben, um dieses Problem zu beheben. Nehmen wir an, Sie erweitern C++ so, dass die Typen oder Bereiche lokaler Variablen durch Operationen an ihnen geändert werden können. Daher verschiebt sich std::safe_move(X) von X und markiert X als abgelaufene Variable, die für den Rest des Gültigkeitsbereichs nicht mehr gültig ist. Herauszufinden, was zu tun ist, wenn eine Variable halb abgelaufen ist (abgelaufen in einem Zweig, aber nicht in einem anderen Zweig), ist eine interessante Frage.

Weil das verrückt ist, können wir es stattdessen als ein Bibliotheksproblem angreifen. Bis zu einem gewissen Grad können wir diese Art von Tricks (eine Variable, deren Typ sich ändert) zur Laufzeit fälschen. Das ist grob, aber gibt die Idee:

template<typename T> 
struct read_once : std::tr2::optional<T> { 
    template<typename U, typename=typename std::enable_if<std::is_convertible<U&&, T>::value>::type> 
    read_once(U&& u):std::tr2::optional<T>(std::forward<U>(u)) {} 
    T move() && { 
    Assert(*this); 
    T retval = std::move(**this); 
    *this = std::tr2::none_t; 
    return retval; 
    } 
    // block operator*? 
}; 

dh schreiben einen linearen Typ, der nur von über move gelesen werden kann, und nach dieser Zeit mit dem Lesen Assert s oder wirft.

ändern Sie dann Ihren Konstruktor:

A::A(read_once<std::string> val): 
    _val(val.move()) 
{ 
    std::cout << val << std::endl; // does not compile 
    std::cout << val.move() << std::endl; // compiles, but asserts or throws 
} 

mit Speditions Konstrukteurs können Sie eine weniger lächerlich Schnittstelle ohne read_once Arten aussetzen, dann uns auf Ihre Konstrukteure auf Ihre „sicher“ (möglicherweise private) Versionen mit read_once<> Wrapper um die Argumente.

Wenn Ihre Tests alle Codepfade decken, werden Sie Assert s statt nur leer std::string ist schön, auch wenn Sie gehen und move mehr als einmal von Ihrem read_once Variablen.

+2

Aber Sie müssen daran denken, all dies zu tun. Wenn Sie sich daran erinnern können, hätten Sie wahrscheinlich den Fehler nicht gemacht. –

+0

@LightnessRacesinOrbit Sure: aber wenn Sie ein robustes 'read_once' hatten und der Regel" immer nehmen 'nach Wert folgen, um' als 'read_once'" kopiert zu werden, würden Sie 'Assert' anstelle von leeren Daten erhalten, wenn Sie es lesen . Und die Pfeife hält die Tiger fern, siehst du irgendwelche Tiger? – Yakk

+0

Und wenn Sie der Regel folgen "versuchen Sie nicht, die falsche Variable zu drucken" dann wäre alles –

0

"Nachdem ich den Wechsel zu C++ 11 gemacht habe, übertrage ich jetzt systematisch meine Strings nach Wert in meinen Konstruktoren."

Es tut mir leid, ich sehe nicht, warum man das tun möchte. Welche Verbesserung bietet dies in Bezug auf die traditionelle Methode? (Das ist im Grunde Bug-Beweis).

 
class A(const std::string & s): 
    _val(s) 
{ 
    std::cout << s << std::endl; // no Bug!!! 
    std::cout << _val << std::endl; // no Bug either !!! 
} 

+0

Um Ihre Frage zu beantworten: Ja, 'std :: move' ist besser als das, weil, nun, weniger Kopien. Außerdem beantwortet Ihre Antwort die Frage nicht. – milleniumbug

+0

Angenommen, Sie 's' sind eine Zeichenfolge mit 100.000 Zeichen. Deine Version erstellt immer eine Kopie davon, immer genau hier: '_val (s)'. Die obige Version wird keine Kopie davon machen, wenn der Aufrufer "std :: move" in den Konstruktor einfügt oder "A" aus einer temporären "std :: string" konstruiert. Siehe [hier] (http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/) für eine tiefere Diskussion. Beachten Sie, dass Sie mit einem 'std :: string && s' und' std :: string const & s'-Konstruktor 1 weniger Bewegung haben als mit 'std :: string s', aber die Menge an Code verdoppelt, die Sie pflegen müssen. – Yakk

+0

In diesem Fall machen wir keine Kopien, da wir durch Verweis gehen. Ich bin mit der zitierten Diskussion sehr vertraut. Es spricht eine Situation an, in der es Kopien gibt, was hier nicht der Fall ist. Die Frage ist: "Was kann ich tun, um die Chancen zu verringern, dass ich falsch liege?". Die Antwort lautet: "Tu das nicht!". Sie reduzieren dadurch keine Kopien und führen einen Fehler ein. –

Verwandte Themen