5

Ich stieß auf ein Youtube-Video auf C++ 11 Nebenläufigkeit (Teil 3) und den folgenden Code, der kompiliert und erzeugt das korrekte Ergebnis im Video.visuelle Studio-Implementierung von "move semantics" und "rvalue reference"

Allerdings habe ich einen Kompilierungsfehler dieses Codes mit Visual Studio 2012. Der Compiler beschwert sich über den Argumenttyp von . Wenn ich den Argumenttyp zu list<double>& ändere, kompilierte der Code.

Meine Frage ist, was von move(list) in _tmain() zurückgegeben wird, ist es eine Rvalue-Referenz oder nur eine Referenz?

#include "stdafx.h" 
#include <iostream> 
#include <thread> 
#include <chrono> 
#include <list> 
#include <algorithm> 
using namespace std; 

void toSin(list<double>&& list) 
{ 
    //this_thread::sleep_for(chrono::seconds(1)); 
    for_each(list.begin(), list.end(), [](double & x) 
    { 
     x = sin(x); 
    }); 

    for_each(list.begin(), list.end(), [](double & x) 
    { 
     int count = static_cast<int>(10*x+10.5); 
     for (int i=0; i<count; ++i) 
     { 
      cout.put('*'); 
     } 
     cout << endl; 
    }); 
}  

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    list<double> list; 

    const double pi = 3.1415926; 
    const double epsilon = 0.00000001; 
    for (double x = 0.0; x<2*pi+epsilon; x+=pi/16) 
    { 
     list.push_back(x); 
    } 

    thread th(&toSin, /*std::ref(list)*/std::move(list)); 
    th.join();  

    return 0; 
} 
+4

VS2012 ist zu alt, versuchen Sie mindestens VS2013 und vorzugsweise die Community-Vorschau für 2015 –

Antwort

-1

Was aus std::move zurückgegeben wird, ist in der Tat eine rvalue Referenz, aber das spielt keine Rolle, weil der thread Konstruktor nicht perfekt Forwarding für seine Argumente nicht verwendet. Zuerst kopiert/verschiebt er sie in den Speicher des neuen Threads. Innerhalb des neuen Threads wird dann die mitgelieferte Funktion mit den Kopien aufgerufen.

Da die Kopien keine temporären Objekte sind, wird dieser Schritt nicht an Rvalue-Referenzparameter gebunden.

Was die Norm sagt (30.3.1.2):

Der neue Thread der Ausführung führt

INVOKE(DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))...) 

mit den Anrufen zu DECAY_COPY in der Konstruktions Thread ausgewertet.

und

An mehreren Stellen in dieser Klausel die Operation DECAY_COPY(x) verwendet wird. Alle diese Verwendungen bedeuten die Funktion decay_copy(x) und verwenden das Ergebnis nennen, wo decay_copy ist wie folgt definiert:

template <class T> decay_t<T> decay_copy(T&& v) 
{ return std::forward<T>(v); } 

Der Wert Kategorie verloren.

+0

Kopiert/verschiebt der Thread die internen Kopien in die functors-Parameter? –

+1

Dies ist ein Fehler in der VS2012-Implementierung richtig? –

+0

@MooingDuck: Warum sollte es sein? Dies ist das für 'std :: thread' angegebene Verhalten. –

6

Dies scheint ein Fehler in MSVC2012 zu sein. (und auf Schnellinspektion, MSVC2013 und MSVC2015)

thread verwendet nicht perfektes Weiterleiten direkt, wie Speichern eines Verweises auf Daten (temporär oder nicht) in dem ursprünglichen Thread und die Verwendung in dem erzeugten Thread wäre extrem fehleranfällig und gefährlich.

Stattdessen kopiert es jedes Argument in interne Daten decay_t<?>.

Der Fehler besteht darin, dass beim Aufruf der Worker-Funktion diese interne Kopie einfach an Ihre Prozedur übergeben wird. Stattdessen sollte es diese internen Daten in den Anruf verschieben.

Diese nicht 19 in Compiler Version behoben zu sein scheint, was ich denke MSVC2015 ist (habe nicht überprüfen), basierend auf Ihren Code kompilieren hier

Dies sowohl dem Wortlaut der Norm beruht (Es soll einen decay_t<F> mit decay_t<Ts>... aufrufen - das bedeutet rvalue binding, nicht lvalue binding, und weil die im Thread gespeicherten lokalen Daten nach dem Aufruf Ihrer Prozedur nie wieder verwendet werden (logisch sollte es als ablaufend behandelt werden) Daten, nicht persistente Daten). Hier

ist eine Arbeit um:

template<class F> 
struct thread_rvalue_fix_wrapper { 
    F f; 
    template<class...Args> 
    auto operator()(Args&...args) 
    -> typename std::result_of<F(Args...)>::type 
    { 
     return std::move(f)(std::move(args)...); 
    } 
}; 
template<class F> 
thread_rvalue_fix_wrapper< typename std::decay<F>::type > 
thread_rvalue_fix(F&& f) { return {std::forward<F>(f)}; } 

dann

thread th(thread_rvalue_fix(&toSin), /*std::ref(list)*/std::move(list)); 

funktionieren sollte. (getestet in MSVC2015 Online-Compiler oben verlinkt) Basierend auf persönlichen Erfahrungen sollte es auch in MSVC2013 funktionieren. Ich weiß nichts über MSVC2012.

+0

Da die Kopie in einem separaten Thread erstellt wird und ** am Ende des Volltexts ** nicht zerstört wird, bedeutet das nicht, dass die Kopien keine Prvalues ​​sind? Und es scheint nicht in irgendeinen der Fälle zu passen, die sie zu xvalues ​​machen würden ... also sind sie überhaupt keine rvalues ​​und können nicht an rvalue-Referenzen binden. –

+1

@BenVoigt Das ist ein Implementierungsdetail. Das Standardmandat lautet 'INVOKE (DECAY_COPY (Std :: Forward (f)), DECAY_COPY (Std :: Forward (args)) ...)' wird vom erstellten Thread aufgerufen. Dem Compiler ist es freigestellt, irgendetwas zu tun, wenn das passiert ist. Da der Rückgabewert von 'DECAY_COPY()' nicht an Lvalues ​​gebunden werden kann und an Rvalues ​​gebunden werden kann, macht MSVC nicht das, was der Standard vorschreibt. Glücklicherweise ist es unmöglich zu erkennen, dass die Argumente für einen Funktionsaufruf Pr-Werte sind, also erzeugt 'std :: move' das erforderliche Ergebnis ohne Compiler-Erweiterungen (glaube ich). – Yakk

+0

Die an INVOKE übergebenen Parameter sind jedoch keine Provisorien. Logischerweise sollte der Standard diesen Fall als x-Wert ausgeben, aber nicht. –