Fragment kodu znajduje się poniżej:
class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX(){ return x; }
};
int main()
{
tFunc t;
thread t1(t);
if(t1.joinable())
{
cout<<"Thread is joining..."<<endl;
t1.join();
}
cout<<"x : "<<t.getX()<<endl;
return 0;
}
Otrzymuję wynik:
Constructed : 0x7ffe27d1b0a4
Destroyed : 0x7ffe27d1b06c
Thread is joining...
Thread running at : 11
Destroyed : 0x2029c28
x : 1
Destroyed : 0x7ffe27d1b0a4
Jestem zdezorientowany, w jaki sposób wywołano destruktory o adresach 0x7ffe27d1b06c i 0x2029c28 i nie wywołano żadnych konstruktorów? Natomiast pierwszy i ostatni konstruktor i destruktor są odpowiednio obiektu, który utworzyłem.
c++
multithreading
destructor
SHAHBAZ
źródło
źródło
Odpowiedzi:
Brakuje instrumentalnej konstrukcji kopii i konstrukcji przenoszenia. Prosta modyfikacja programu dostarczy dowodów na to, gdzie mają miejsce konstrukcje.
Kopiuj konstruktora
Wyjście (adresy się różnią)
Kopiuj konstruktora i przenieś konstruktora
Jeśli podasz ctor ruchu, będzie on preferowany dla co najmniej jednego z tych kopii:
Wyjście (adresy się różnią)
Referencje zapakowane
Jeśli chcesz ominąć te kopie, możesz owinąć swój program wywołujący w wrapper referencyjny (
std::ref
). Ponieważ chcesz wykorzystaćt
po zakończeniu gwintowania, jest to opłacalne w twojej sytuacji. W praktyce należy zachować szczególną ostrożność podczas łączenia wątków z odwołaniami do obiektów wywołujących, ponieważ czas życia obiektu musi trwać co najmniej tak długo, jak wątek wykorzystujący odwołanie.Wyjście (adresy się różnią)
Zauważ, że chociaż zachowałem przeciążenia copy-ctor i move-ctor, żadne z nich nie zostało wywołane, ponieważ opakowanie referencyjne jest teraz przedmiotem kopiowania / przenoszenia; nie rzecz, do której się odnosi. Ponadto to końcowe podejście zapewnia to, czego prawdopodobnie szukałeś;
t.x
z powrotemmain
jest w rzeczywistości zmodyfikowany do11
. Nie było w poprzednich próbach. Nie mogę tego jednak wystarczająco podkreślić: bądź ostrożny . Żywotność obiektu ma kluczowe znaczenie .Rusz się i nic więcej
Wreszcie, jeśli nie jesteś zainteresowany zachowaniem,
t
tak jak w przykładzie, możesz użyć semantyki move, aby wysłać instancję bezpośrednio do wątku, poruszając się po drodze.Wyjście (adresy się różnią)
Tutaj możesz zobaczyć, że obiekt jest tworzony, odwołanie do wartości tego samego - a następnie wysyłane prosto do
std::thread::thread()
, gdzie jest ponownie przenoszone do ostatecznego miejsca spoczynku, którego właścicielem jest wątek od tego momentu. Zaangażowani są nie kopiownicy. Rzeczywiste kropki opierają się o dwie skorupy i konkretny obiekt docelowy.źródło
Co do twojego dodatkowego pytania zamieszczonego w komentarzach:
Konstruktor
std::thread
first tworzy kopię swojego pierwszego argumentu (bydecay_copy
) - to tam wywoływany jest konstruktor kopii . (Należy pamiętać, że w przypadku rvalue argumentów, takich jakthread t1{std::move(t)};
lubthread t1{tFunc{}};
, konstruktor posunięcie byłoby nazwać zamiast).Wynikiem
decay_copy
jest tymczasowe, które znajduje się na stosie. Ponieważ jednakdecay_copy
jest wykonywany przez wątek wywołujący , ten tymczasowy rezyduje na stosie i jest niszczony na końcustd::thread::thread
konstruktora. W związku z tym sam plik tymczasowy nie może być bezpośrednio używany przez nowo utworzony wątek.Aby „przekazać” funktor do nowego wątku, nowy obiekt musi zostać utworzony gdzieś indziej i tutaj wywoływany jest konstruktor ruchu . (Gdyby nie istniał, zamiast tego wywoływany byłby konstruktor kopii).
Zauważmy, że możemy się zastanawiać, dlaczego nie zastosowano tutaj odroczonej tymczasowej materializacji . Na przykład w tym demo na żywo wywoływany jest tylko jeden konstruktor zamiast dwóch. Uważam, że niektóre wewnętrzne szczegóły implementacji biblioteki standardowej C ++ utrudniają optymalizację, którą należy zastosować dla
std::thread
konstruktora.źródło