2017-11-09 4 views
3

Ich habe eine std::packaged_task mit einem Lambda, die eine Variable durch Kopie erfasst. Wenn dieses std::packaged_task gelöscht wird, würde ich erwarten, dass die Variable innerhalb des Lambda zerstört wird, aber ich bemerkte, dass, wenn ich das zugehörige std::future für dieses std::packaged_task bekomme, das future Objekt die Lebensdauer der Variable innerhalb des Lambda verlängert.Wie wirkt sich std :: future auf die Lebensdauer einer zugeordneten std :: packaged_task aus?

Zum Beispiel:

#include <iostream> 
#include <future> 

class Dummy 
{ 
public: 
    Dummy() {std::cout << this << ": default constructed;" << std::endl;} 
    Dummy(const Dummy&) {std::cout << this << ": copy constructed;" << std::endl;} 
    Dummy(Dummy&&) {std::cout << this << ": move constructed;" << std::endl;} 
    ~Dummy() {std::cout << this << ": destructed;" << std::endl;} 
}; 

int main() 
{ 
    std::packaged_task<void()>* p_task; 
    { 
     Dummy ScopedDummy; 
     p_task = new std::packaged_task<void()>([ScopedDummy](){std::cout << "lambda call with: " << &ScopedDummy << std::endl;}); 
     std::cout << "p_task completed" << std::endl; 
    } 
    { 
     std::future<void> future_result; 
     { 
      future_result = p_task->get_future(); 
      (*p_task)(); 
      delete p_task; 
     } 
     std::cout << "after p_task has been deleted, the scope of future_result determines the scope of the dummy inside p_task" << std::endl; 
    } 
    std::cout << "p_task cleans up when future_result dies" << std::endl; 
} 

Eine mögliche Ausgabe lautet:

0x7fff9cf873fe: default constructed; 
0x7fff9cf873ff: copy constructed; 
0x1904b38: move constructed; 
0x7fff9cf873ff: destructed; 
0x7fff9cf873fe: destructed; 
lambda call with: 0x1904b38 
after p_task has been deleted, the scope of future_result determines the scope of the dummy inside p_task 
0x1904b38: destructed; 
p_task cleans up when future_result dies 

So das Objekt innerhalb der Lambda hat seine Lebensdauer durch den Umfang der future_result erweitert.

Wenn wir die Linie future_result = p_task->get_future(); eine mögliche Ausgabe ist auf Kommentar:

0x7fff57087896: default constructed; 
0x7fff57087897: copy constructed; 
0x197cb38: move constructed; 
0x7fff57087897: destructed; 
0x7fff57087896: destructed; 
lambda call with: 0x197cb38 
0x197cb38: destructed; 
after p_task has been deleted, the scope of future_result determines the scope of the dummy inside p_task 
p_task cleans up when future_result dies 

ich gefragt haben, was Mechanismus hier Spiel kommt in, tut std::future enthält einige Links, die zugehörigen Objekte am Leben hält?

+0

welchen Compiler benutzen Sie? clang erzeugt die erwartete Ausgabe, dh die Aufgabe wird zerstört, wenn die paketierte Aufgabe ... ist. Ihr Compiler sieht die Aufgabe als Teil des gemeinsamen Status an. Das sieht für mich nicht korrekt aus ... –

+0

Ich benutze gcc 7.2.0. Sie haben recht, cla2ng erzeugt wirklich die zweite Ausgabe. Denkst du, das würde als Fehler gelten? –

+0

ok, [gcc libstdC++ sources] (https://gcc.gnu.org/onlinedocs/gcc-7.2.0/libstdc++/api/a00074_source.html#l01476) zeigt, dass die Aufgabe tatsächlich im Shared-State gespeichert ist. Das scheint mir falsch zu sein, obwohl der Standard das Speichern einer Kopie im Shared-State nicht ausdrücklich verbietet ... nun, mal sehen, was andere sagen ... –

Antwort

4

Blick auf gcc7.2.0 packaged_task sources lesen wir:

packaged_task(allocator_arg_t, const _Alloc &__a, _Fn &&__fn) 
    : _M_state(__create_task_state<_Res(_ArgTypes...)>(std::forward<_Fn>(__fn), __a)){} 

~packaged_task() 
{ 
    if (static_cast<bool>(_M_state) && !_M_state.unique()) 
    _M_state->_M_break_promise(std::move(_M_state->_M_result)); 
} 

wo _M_state ist ein shared_ptr auf den internen packaged_task gemeinsamen Staat. Also, es stellt sich heraus, dass gcc speichert die abrufbar als Teil der gepackten_task freigegebenen Zustand, damit Bindung der aufrufbaren Lebenszeit an wen unter paked_task, Zukunft, shared_future stirbt zuletzt.

im Vergleich Klirren nicht der Fall, die aufrufbar, wenn die verpackte Aufgabe zerstört wird zerstört (in der Tat, speichert mein Exemplar von Klirren der abrufbaren als Eigen Mitglied).

Wer hat Recht? der Standard ist nicht sehr klar über die Lebensdauer der gespeicherten Aufgaben; von einer Seite haben wir

[[futures.task]]

packaged_task einen Typ für definiert so eine Funktion oder aufrufbare Objekt Einwickeln dass der Rückgabewert der Funktion oder aufrufbare Objekt in einer Zukunft gespeichert, wenn es aufgerufen wird.

packaged_task (F & & f) [...] Konstruiert ein neues packaged_task Objekt mit einem gemeinsamen Zustand und initialisiert das gespeicherte Aufgabe des Objekts mit std :: Vorwärts (f).

packaged_task (packaged_task & & rhs) [...] Verschiebt die gespeicherte Aufgabe von rhs * this.

reset() [...] Effekte: Als ob * dies = packaged_task (std :: move (f)), wobei f ist die Aufgabe in * dieser gespeichert.

, die das aufrufbare schlägt durch die packaged_task gehört, aber wir haben auch

[[futures.state]]

-Viele der in diesem Unterabschnitt eingeführt Klassen einige verwenden Status, um Ergebnisse zu kommunizieren. Dieser gemeinsame Status besteht aus einigen Statusinformationen und einigen (möglicherweise noch nicht bewerteten) Ergebnissen, die ein (möglicherweise ungültiger) Wert oder eine Ausnahme sein können. [Hinweis: In dieser Klausel definierte Futures, Versprechen und Aufgaben verweisen auf einen solchen gemeinsamen Status. -endnote]

- [Anmerkung: Das Ergebnis jede Art von Objekt sein kann eine Funktion einschließlich dieses Ergebnis zu berechnen, wie durch async [...]]

und

[futures.task.members]

-packaged_task (F & & f), [...] verhalten soll eine Kopie von f aufrufen das gleiche wie das Aufrufen von f [...] - ~ packaged_task(); Effekte: Bricht alle freigegebenen Zustand

darauf hindeutet, dass eine aufrufbare im freigegebenen Zustand gespeichert werden können, und dass sollte man nicht verlassen sich auf jeden aufrufbar pro Instanz Verhalten (dies interpretiert werden kann, die aufrufbar Ende der Lebensdauer Seite schließen -Effekte, das impliziert übrigens auch, dass Ihre Callable nicht streng gültig ist, weil sie sich anders verhält als ihre Kopie); außerdem wird nichts über die gespeicherte Aufgabe im dtor erwähnt.

Insgesamt denke ich, dass clang folgt dem Wortlaut konsequenter, obwohl nichts scheint explizit verbieten gcc Verhalten. Das heißt, ich stimme zu, sollte dies besser dokumentiert werden, weil es sonst zu überraschenden Bugs führen kann ...

Verwandte Themen