【问题标题】:How to wrap a call to `std::thread` constructor? (that works with gcc, VS and icpc)如何包装对`std::thread`构造函数的调用? (适用于 gcc、VS 和 icpc)
【发布时间】:2014-05-08 00:54:25
【问题描述】:

原帖(有错误)

我想包装对 std::thread 构造函数的调用(以跟踪所有正在运行的线程,以便我可以加入它们或做其他事情)。在此示例中,t1 线程构造正确,但t2 线程未使用 gcc 4.8.1。但是,在 Windows (VS2012) 上,它编译没有错误,运行也没有错误。根据here 的讨论,这可能看起来是 gcc 中的一个错误,但可以说它实际上是 VS 中的一个错误。这样做的正确方法是什么?

#include <iostream>
#include <thread>

class A {
public:
    void foo(int n ) { std::cout << n << std::endl; }
};

template<class F, class Arg>
std::thread& wrapper(F&& f, Arg&& a)
{
   std::thread* t = new std::thread(f,a,100);
   return *t;
}

int main()
{
    A a;

    std::thread t1(&A::foo, &a, 100);
    t1.join();

    std::thread t2 = wrapper(&A::foo, &a);
    t2.join();

    return 0;
}

这是编译器错误

-bash-4.1$ make
g++ -std=c++11    main.cpp   -o main
main.cpp: In function ‘int main()’:
main.cpp:23:41: error: use of deleted function ‘std::thread::thread(std::thread&)’
     std::thread t2 = wrapper(&A::foo, &a);
                                         ^
In file included from main.cpp:2:0:
/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/thread:125:5: error: declared here
     thread(thread&) = delete;
     ^
make: *** [all] Error 1

更新

我在这里问错了问题,准备删除它,但是因为答案很有帮助,所以我会留下它。问题是 Intel icpc 14.0 编译器(不是 gcc 4.8.1)是 throwing the same error regarding bind as discussed here。它与“包装器”无关,只是用成员函数而不是静态函数调用 std::thread。

有关内存泄漏的投诉是 100% 有效的,但不幸的是,我对示例进行了简化(来自我的真实代码)。实际代码将线程保存在容器中,并在销毁时删除线程。

这是一个更好的例子:

#include <iostream>
#include <thread>

class A {
public:
    void foo() { }

    template<class Function, class Arg>
    std::thread* wrapper(Function&& f, Arg&& a)
    {
        auto t = new std::thread(f,a);
        return t;
    }
};

int main()
{
    A a;

    std::thread t1(&A::foo, &a);
    t1.join();

    std::thread* t2 = a.wrapper(&A::foo, &a);
    t2->join();
    delete t2;

    return 0;
}

g++ (4.8.1) 的输出有效

-bash-4.1$ make CXX=g++
g++ -lpthread -std=c++11    main.cpp   -o main

英特尔编译器 icpc (14.0) 的输出不起作用

-bash-4.1$ make CXX=icpc
icpc -lpthread -std=c++11    main.cpp   -o main
/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/functional(1697): error: class "std::result_of<std::_Mem_fn<void (A::*)()> (A *)>" has no member "type"
        typedef typename result_of<_Callable(_Args...)>::type result_type;
                                                         ^
          detected during:
            instantiation of class "std::_Bind_simple<_Callable (_Args...)> [with _Callable=std::_Mem_fn<void (A::*)()>, _Args=<A *>]" at line 1753
            instantiation of "std::_Bind_simple_helper<_Callable, _Args...>::__type std::__bind_simple(_Callable &&, _Args &&...) [with _Callable=void (A::*)(), _Args=<A *>]" at line 137 of "/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/thread"
            instantiation of "std::thread::thread(_Callable &&, _Args &&...) [with _Callable=void (A::*)(), _Args=<A *>]" at line 20 of "main.cpp"

/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/functional(1726): error: class "std::result_of<std::_Mem_fn<void (A::*)()> (A *)>" has no member "type"
          typename result_of<_Callable(_Args...)>::type
                                                   ^
          detected during:
            instantiation of class "std::_Bind_simple<_Callable (_Args...)> [with _Callable=std::_Mem_fn<void (A::*)()>, _Args=<A *>]" at line 1753
            instantiation of "std::_Bind_simple_helper<_Callable, _Args...>::__type std::__bind_simple(_Callable &&, _Args &&...) [with _Callable=void (A::*)(), _Args=<A *>]" at line 137 of "/opt/rh/devtoolset-2/root/usr/include/c++/4.8.1/thread"
            instantiation of "std::thread::thread(_Callable &&, _Args &&...) [with _Callable=void (A::*)(), _Args=<A *>]" at line 20 of "main.cpp"

compilation aborted for main.cpp (code 2)
make: *** [all] Error 2
-bash-4.1$

【问题讨论】:

  • 这是一个糟糕的包装。为什么要动态分配对象?!
  • 您当前的 Wrapper 也在泄漏内存。您正在调用 new 来创建线程对象,但您没有调用 delete 来释放内存。
  • 不,这绝对是 Visual Studio 中的一个错误,与您提供的链接 100% 无关,这与 std::ref 有关,而您的问题是关于线程复制构造函数。
  • @KerrekSB - 同意这是一个糟糕的包装器。这是一个不好的例子,但问题不在于内存泄漏,而是我无法使用 icpc 构造 std::thread ,如图所示。

标签: c++ c++11 stdthread icc


【解决方案1】:

你的包装应该是这样的:

template<class F, class Arg>
std::thread wrapper(F&& f, Arg&& a)
{
    return std::thread(std::forward<F>(f), std::forward<Arg>(a));
}

【讨论】:

  • 这对 Intel 编译器(icpc 版本 14.0)没有影响。
【解决方案2】:

std::thread 不可复制。鉴于wrapper 返回一个std::thread&amp;wrapper(&amp;A::foo, &amp;a); 是一个左值,因此t2 的初始化需要一个副本。这就是编译器错误的原因。

遗憾的是,某些版本的 Visual Studio 会很乐意在这里执行移动,即使不涉及右值。

std::thread* t = new std::thread(f,a,100); return *t; 之类的东西与return *new std::thread(f,a,100); 几乎相同,这清楚地表明了memory leak operator*new。不要这样做。

虽然不可复制,但std::thread 是可移动的。您只需要使wrapper(&amp;A::foo, &amp;a); 成为右值即可。这可以通过以最自然的风格编写函数来完成,如下所示:

template<class F, class Arg>
std::thread wrapper(F&& f, Arg&& a)
{
    return std::thread(f, a, 100);
}

然而,这无法正确转发保留其值类别的参数(因为它会将右值转换为左值),即使对于这个特定示例来说这不是问题。为了具有普遍适用性,函数体应正确转发参数,如下所示:return std::thread(std::forward&lt;F&gt;(f), std::forward&lt;Arg&gt;(a), 100);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-29
    相关资源
    最近更新 更多