아래 코드 스 니펫을 찾으십시오.
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;
}
내가 얻는 결과는 다음과 같습니다.
Constructed : 0x7ffe27d1b0a4
Destroyed : 0x7ffe27d1b06c
Thread is joining...
Thread running at : 11
Destroyed : 0x2029c28
x : 1
Destroyed : 0x7ffe27d1b0a4
주소가 0x7ffe27d1b06c 및 0x2029c28 인 소멸자가 어떻게 호출되고 생성자가 호출되지 않은지 혼란 스럽습니다. 첫 번째와 마지막 생성자와 소멸자는 각각 내가 만든 객체의 것입니다.
답변
계측 복사 구성 및 이동 구성이 누락되었습니다. 프로그램을 간단하게 수정하면 시공이 진행되고있는 증거가 제공됩니다.
생성자 복사
#include <iostream>
#include <thread>
#include <functional>
using namespace std;
class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
tFunc(tFunc const& obj) : x(obj.x)
{
cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX() const { 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;
}
출력 (주소가 다름)
Constructed : 0x104055020
Copy constructed : 0x104055160 (source=0x104055020)
Copy constructed : 0x602000008a38 (source=0x104055160)
Destroyed : 0x104055160
Thread running at : 11
Destroyed : 0x602000008a38
Thread is joining...
x : 1
Destroyed : 0x104055020
생성자 복사 및 이동 생성자
당신이 이동 ctor를 제공하는 경우 다른 사본 중 하나 이상이 선호됩니다.
#include <iostream>
#include <thread>
#include <functional>
using namespace std;
class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
tFunc(tFunc const& obj) : x(obj.x)
{
cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
}
tFunc(tFunc&& obj) : x(obj.x)
{
cout<<"Move constructed : "<<this<< " (source=" << &obj << ')' << endl;
obj.x = 0;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX() const { 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;
}
출력 (주소가 다름)
Constructed : 0x104057020
Copy constructed : 0x104057160 (source=0x104057020)
Move constructed : 0x602000008a38 (source=0x104057160)
Destroyed : 0x104057160
Thread running at : 11
Destroyed : 0x602000008a38
Thread is joining...
x : 1
Destroyed : 0x104057020
감싸 인 참조
이러한 복사본을 피하려면 호출자를 참조 래퍼 ( std::ref
)로 래핑 할 수 있습니다 . t
스레딩 부분이 완료된 후 활용하고 싶기 때문에 상황에 따라 적합합니다. 실제로 객체의 수명이 최소한 스레드가 참조를 사용하는 한 연장되어야하므로 호출 객체에 대한 참조를 스레딩 할 때는 매우 주의해야합니다.
#include <iostream>
#include <thread>
#include <functional>
using namespace std;
class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
tFunc(tFunc const& obj) : x(obj.x)
{
cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
}
tFunc(tFunc&& obj) : x(obj.x)
{
cout<<"Move constructed : "<<this<< " (source=" << &obj << ')' << endl;
obj.x = 0;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX() const { return x; }
};
int main()
{
tFunc t;
thread t1{std::ref(t)}; // LOOK HERE
if(t1.joinable())
{
cout<<"Thread is joining..."<<endl;
t1.join();
}
cout<<"x : "<<t.getX()<<endl;
return 0;
}
출력 (주소가 다름)
Constructed : 0x104057020
Thread is joining...
Thread running at : 11
x : 11
Destroyed : 0x104057020
copy-ctor 및 move-ctor 오버로드를 유지했지만 참조 래퍼가 복사 / 이동되는 것이기 때문에 호출되지 않았습니다. 그것이 참조하는 것이 아닙니다. 또한,이 마지막 접근 방식은 아마도 당신이 찾고 있던 것을 제공합니다. t.x
다시 main
, 사실에 수정 11
. 이전 시도에는 없었습니다. 그러나 이것을 충분히 강조 할 수는 없습니다 : 조심하세요 . 개체 수명이 중요 합니다.
움직이고 아무것도하지만
마지막으로 t
예제에서와 같이 유지에 관심이 없다면 move semantics를 사용하여 인스턴스를 스레드로 똑바로 보내면서 길을 따라 이동할 수 있습니다.
#include <iostream>
#include <thread>
#include <functional>
using namespace std;
class tFunc{
int x;
public:
tFunc(){
cout<<"Constructed : "<<this<<endl;
x = 1;
}
tFunc(tFunc const& obj) : x(obj.x)
{
cout<<"Copy constructed : "<<this<< " (source=" << &obj << ')' << endl;
}
tFunc(tFunc&& obj) : x(obj.x)
{
cout<<"Move constructed : "<<this<< " (source=" << &obj << ')' << endl;
obj.x = 0;
}
~tFunc(){
cout<<"Destroyed : "<<this<<endl;
}
void operator()(){
x += 10;
cout<<"Thread running at : "<<x<<endl;
}
int getX() const { return x; }
};
int main()
{
thread t1{tFunc()}; // LOOK HERE
if(t1.joinable())
{
cout<<"Thread is joining..."<<endl;
t1.join();
}
return 0;
}
출력 (주소가 다름)
Constructed : 0x104055040
Move constructed : 0x104055160 (source=0x104055040)
Move constructed : 0x602000008a38 (source=0x104055160)
Destroyed : 0x104055160
Destroyed : 0x104055040
Thread is joining...
Thread running at : 11
Destroyed : 0x602000008a38
여기에서 객체가 생성되고 위에서 언급 한 것과 동일한 rvalue 참조가로 전송 std::thread::thread()
된 것을 볼 수 있습니다. 복제본이 없습니다. 실제 dtor는 2 개의 포탄과 최종 목적지 콘크리트 물체에 대한 것입니다.
답변
의견에 게시 된 추가 질문은 다음과 같습니다.
이동 생성자는 언제 호출됩니까?
std::thread
first 의 생성자는 첫 번째 인수의 사본 (by decay_copy
)을 작성합니다. 즉, copy 생성자 가 호출됩니다. (AN의 경우에는주의 를 rvalue의 같은 인수, thread t1{std::move(t)};
또는 thread t1{tFunc{}};
, 이동 생성자가 대신 호출 될 것이다.)
결과 는 스택에 decay_copy
있는 임시 입니다. 그러나 호출 스레드에decay_copy
의해 수행 되므로이 임시는 스택에 상주하며 생성자 끝에서 소멸됩니다 . 결과적으로 임시 자체는 새로 작성된 스레드에서 직접 사용할 수 없습니다.std::thread::thread
functor를 새 스레드로 “전달”하려면 다른 곳 에서 새 객체를 만들어야합니다 . 여기에서 이동 생성자 가 호출됩니다. 존재하지 않으면 대신 복사 생성자가 호출됩니다.
왜 지연된 임시 구체화 가 여기에 적용되지 않는지 궁금 할 수 있습니다. 예를 들어,이 라이브 데모 에서는 두 개가 아닌 하나의 생성자 만 호출됩니다. C ++ 표준 라이브러리 구현에 대한 일부 내부 구현 세부 사항은 std::thread
생성자에 적용되는 최적화를 방해한다고 생각합니다 .