【问题标题】:How to correctly use unique_ptr for Polymorphism in multi-thread? [closed]如何在多线程中正确使用 unique_ptr 进行多态性? [关闭]
【发布时间】:2020-10-21 23:59:08
【问题描述】:

A类是基本类,B是A的继承类,q是unique_ptr(class A)类型的队列。我想从q中获取元素,并为那些继承类调用特殊函数。 这是我的代码:

class A{
public:
    virtual void doSomeWork(){
        std::cout<< "doSomeBasicWork"<<std::endl;
    }
};

class B:public A{
public:
    void doSomeWork(){
        std::cout<< "doSomeSpecificWork"<<std::endl;
    }
};

int main(){
    std::unique_ptr<A> p=std::make_unique<B>();
    std::queue<std::unique_ptr<A>> q;
    q.push(std::move(p));
    auto x=std::move(q.front());
    x->doSomeWork();//print “doSomeSpecificWork”
}

如您所见,在单线程中一切都很好。但是当我在多线程下重写时:

#include <stdio.h>
#include <time.h>
#include <iomanip>
#include <windows.h>
#include<thread>
#include <iostream>
#include <queue>


class A{
public:
    virtual void doSomeWork(){
        std::cout<< "doSomeBasicWork"<<std::endl;
    }
};

class B:public A{
public:
    void doSomeWork(){
        std::cout<< "doSomeSpecificWork"<<std::endl;
    }
};

void producer(std::queue<std::unique_ptr<A>>& q){
        Sleep(1000);
        auto a=std::make_unique<A>();
        q.push(std::move(a));
}

void consumer(std::queue<std::unique_ptr<A>>& q){
        Sleep(1500);
        auto p=std::move(q.front());
        q.pop();
        p->doSomeWork();
}


int main(){
    auto q=std::queue<std::unique_ptr<A>>();
    auto t1=std::thread(consumer,std::ref(q));
    auto t2=std::thread(producer,std::ref(q));
    t1.join();
    t2.join();
}

输出将是 "doSomeBasicWork" ,多态性消失了。 我应该怎么做才能解决这个问题?

【问题讨论】:

  • 对于线程,您使用std::make_unique&lt;A&gt;(),而在第一种情况下使用std::make_unique&lt;B&gt;()。投票关闭为错字。另请注意,std::queue 不是线程安全的。您不能在没有同步的情况下在多个线程中使用它。 Sleep() 不提供。
  • 你有一个数据竞争,所以你的程序的行为是未定义的。您需要同步对队列的访问,例如通过锁定std::mutex
  • make_unique&lt;A&gt; 总是构造一个A。如果要将指向B 的指针存储在unique_ptr&lt;B&gt; 中,则需要明确指定该变量是unique_ptr&lt;B&gt;,而不是auto(然后您可以根据喜好以几种不同的方式构造它)。但是有人可能会问,如果容器中仅包含 unique_ptr&lt;B&gt;,那么为什么不直接在容器中使用,此时需要更多上下文。
  • 旁白:你可以q.push(std::make_unique&lt;B&gt;());

标签: c++ multithreading unique-ptr


【解决方案1】:

正如所指出的,根本原因是一个错字。但是,我强烈怀疑输出将是相同的。这是因为您使用std::make_unique。这将创建一个新实例,但具有基本类型A。你可以阅读here,重要的部分是:

构造一个 T 类型的对象并将其包装在 std::unique_ptr 中。

要解决这个问题,您必须将函数模板化,将具体类型提供给 std::make_unique

类似这样的:

template<class TType>
void producer(std::queue<std::unique_ptr<A>>& q){
        Sleep(1000);
        auto a=std::make_unique<TType>();
        q.push(std::move(a));
}

而且,正如其他人所指出的,您需要使用某种锁定机制,否则您将获得竞争条件。看看herehere

【讨论】:

  • 我看不出模板有什么帮助。他们希望拥有unique_ptr&lt;A&gt;queue,但要存储make_unique&lt;B&gt;() 的结果。他们至少需要 2 个模板类型参数。您的代码只允许他们将TType 的实例存储在queue&lt;unique_ptr&lt;TType&gt;&gt; 中,这与他们似乎想要的相矛盾。但是为什么不只是硬编码类型,而是明确地构造正确的类型呢?如果他们只需要 AB 的组合,使用 2 种类型进行模板只会增加更多复杂性而没有明显的好处。
  • 传递unique_ptr&lt;A&gt;s 的queue 并使用std::make_unique&lt;TType&gt; 推送怎么样?
  • 这实际上可以工作,是的,与当前代码不同。但是为什么不直接用std::make_unique&lt;B&gt; 推送呢?这可以转换为队列中的unique_ptr&lt;A&gt;。所以不需要模板,只需要正确的语法来创建B
  • 我想A 在某一时刻会有更多的子类型?顺便说一句,我已经相应地编辑了我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-26
  • 2012-11-08
  • 2019-12-27
相关资源
最近更新 更多