함수 객체를 사용하는 C ++ 스레드, 여러 소멸자가 어떻게 호출되지만 생성자는 아닌가?


15

아래 코드 스 니펫을 찾으십시오.

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 인 소멸자가 어떻게 호출되고 생성자가 호출되지 않은지 혼란 스럽습니다. 첫 번째와 마지막 생성자와 소멸자는 각각 내가 만든 객체의 것입니다.


11
copy-ctor와 move-ctor도 정의하고 계측하십시오.
WhozCraig

잘 이해했습니다. 복사 생성자가 호출되는 객체를 전달하고 있으므로 맞습니까? 그러나 이동 생성자는 언제 호출됩니까?
SHAHBAZ

답변:


18

계측 복사 구성 및 이동 구성이 누락되었습니다. 프로그램을 간단하게 수정하면 시공이 진행되고있는 증거가 제공됩니다.

생성자 복사

#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 개의 포탄과 최종 목적지 콘크리트 물체에 대한 것입니다.


5

의견에 게시 된 추가 질문은 다음과 같습니다.

이동 생성자는 언제 호출됩니까?

std::threadfirst 의 생성자는 첫 번째 인수의 사본 (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생성자에 적용되는 최적화를 방해한다고 생각합니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.