带有线程的C ++类 - 析构函数,构造函数,移动构造函数?

时间:2015-04-22 13:46:12

标签: c++ multithreading c++11 constructor move-semantics

我正在阅读有关C ++的书Concurrency in action。有一个名为scoped_thread(第27页)的类的示例,它确保 RAII 成语,并且线程在其各自的范围结束之前已加入。

此当前示例不允许将其与最需要移动运算符的函数一起使用,例如emplace_back(?)的vector成员函数。这样,您可以调用类构造函数,并可能允许比push_back更好的优化。因此,我想在类scoped_thread中添加移动构造函数。

在我提交材料之前,这两个问题如下

  1. 非并行构造函数和移动构造函数调用的原因是什么?如何减少它们?
  2. 我的移动构造函数是否正确?
  3. 我现在打电话给joinable()两次。最初它是在构造函数中。但是自从我创建了移动构造函数之后,我也必须在析构函数中检查joinable()。如果我的移动构造函数不好,这可能与问题2相关
  4. 在继续

    之前,请参阅页面底部的EDIT

    不用多说,这是我的类代码(下面提供了在线编译器中完整代码的链接)

    CLASS SCOPED_THREAD

    #define PRINT(X) std::cout << X << std::endl;
    
    static unsigned dtor_count = 0;
    static unsigned ctor_count = 0;
    static unsigned mtor_count = 0;
    
    class scoped_thread {
        thread t;
    public:
        explicit scoped_thread(thread t_) :
            t(std::move(t_))
        {
            if (!t.joinable())
                throw std::logic_error("No thread!");
            PRINT("Called scoped_thread CTor");
            ctor_count++;
        }
        ~scoped_thread()
        {
            if (t.joinable())
                t.join();
            PRINT("Called scope_thread DTor");
            dtor_count++;
        }
        scoped_thread(const scoped_thread&) = delete; // copy CTor
        scoped_thread& operator=(const scoped_thread&) = delete; // copy init
        scoped_thread(scoped_thread&& s) {
            t = std::move(s.t);
            mtor_count++;
        };
    };
    

    静态变量用作对构造函数,析构函数和移动构造函数的调用次数的计数器。

    这是一个示例用法。 (注意:评论部分是没有这个课程可以实现的目标)

    示例使用

    void do_work(int i) { PRINT("Made Thread : " << i); };
    void thread_vector_example()
    {
        // normal version: have to call thread join
        /*
        std::vector<std::thread> threads;
        for (unsigned i = 0; i < 10; ++i)
            threads.push_back(std::thread(do_work, i));
        std::for_each(begin(threads), end(threads),
            std::mem_fn(&std::thread::join));   
        */
    
        std::vector<scoped_thread> sthreads;
        for (unsigned i = 0; i < 10; ++i) {
            sthreads.emplace_back(std::thread(do_work, i));
        }   
    }   
    
    1. 来自 g ++ 编译器(下面给出的链接)和选项g++ -std=c++14 -O2 -Wall -pthread,结果如下

      • 构造函数:10
      • destructors叫:25
      • 移动构造函数:15
    2. 从Visual Studio C ++编译器(标准选项)

      • 构造函数:10
      • destructors叫:35
      • 移动构造函数:25
    3. 链接到文件 - &gt; http://coliru.stacked-crooked.com/a/33ce9f3daab4dfda

      修改

      在调用保留函数,即sthreads.reserve(10)之后,我可以有一个更好的版本,它只调用构造函数和析构函数10次。但是,当我从类中删除move构造函数时仍然存在问题,代码将无法编译

1 个答案:

答案 0 :(得分:3)

  
      
  1. 非并行构造函数和移动构造函数调用的原因是什么?如何减少它们?
  2.   

10个构造函数调用的原因是你构造了10个对象......

正如您已经想到的那样,移动构造函数调用的原因是由于超出容量而重新分配了向量的内部存储,您可以通过预先保留足够的容量来摆脱它们。 / p>

  
      
  1. 我的移动构造函数是否正确?
  2.   

是。一旦停止调试构造函数计数,scoped_thread(scoped_thread&& s) = default;就足够了。

  

我现在两次调用joinable()。最初它是在构造函数中。但是因为我创建了移动构造函数,所以我也必须在析构函数中检查joinable()。如果我的移动构造函数不好,这可能与问题2相关

移动的可能性确实需要在析构函数中进行测试。但是,由于它现在已经在析构函数中进行了测试,因此在我看来,在构造函数中测试它没有任何意义。

  

但是,当我从类中删除move构造函数时仍然存在问题,代码将无法编译

即使您以不会导致移动的方式使用向量(保留空间并放置新对象),对象仍然必须是可移动的。编译器不能简单地检查您在运行时是否永远不会超过容量,因此不会生成重新分配的代码,这取决于可移动或可复制的类型。

这是一个额外的想法,因为你想避免移动。不要将线程移动到构造函数中,而是将其构造为原位(您可能仍希望提供移动线程的构造):

template<class Fun, class... Args> 
explicit scoped_thread(Fun&& f, Args&&... args):
t(std::forward<Fun>(f), std::forward<Args>(args)...)
{}

//...
sthreads.emplace_back(do_work, i);