【问题标题】:std::async and object copystd::async 和对象复制
【发布时间】:2013-02-16 15:55:25
【问题描述】:

我正在尝试使用std::async 并最终得到一个看起来像这样的代码:

class obj {
public:
    int val;

    obj(int a) : val(a) {
        cout << "new obj" << endl;
    }
    ~obj() {
        cout << "delete obj" << endl;
    }
};


void foo(obj a) {

    this_thread::sleep_for(chrono::milliseconds(500));
    cout << a.val << endl;
}

int main(int argc, int **args) {

    obj a(5);
    auto future = async(foo, a);
    future.wait();

    return 0;
}

结果是:

new obj
delete obj
delete obj
delete obj
5
delete obj
delete obj
delete obj

然后我尝试将void foo(obj a) 更改为void foo(obj &amp;a)

new obj
delete obj
delete obj
delete obj
5
delete obj
delete obj

为什么要为这个简单的代码制作我的对象的 5 个副本? 我不得不承认,我真的很困惑。有人愿意解释一下吗?

编辑

我正在使用 VS2012

【问题讨论】:

  • 尝试在obj类中复制或移动构造函数。
  • 虽然他看到了复制构造函数的效果,但知道为什么还有 5 个额外的副本仍然会很有趣。我按照代码的简单思维可以看到三个(可能是 4 个)明显的,但至少有两个不是立即显而易见的。
  • 在 GCC 4.7 上使用 const obj&amp; 作为 foo 的参数,我得到 3 个“删除 obj”,这意味着正在制作 2 个副本。
  • @JoachimPileborg 复制构造函数被调用的次数与删除次数一样多。但我更想知道为什么多次调用复制构造函数,以及如何防止这种情况(如果它是一个更大的对象,那就太浪费了)
  • 您是否使用启用优化进行编译?

标签: c++ c++11


【解决方案1】:

在您的情况下,obj 正在被复制:

  1. 两次致电std::async
  2. 两次由async 内部调用std::bind
  3. 一次调用void foo(obj a),因为它需要a 的值。

信不信由你,复制的数量居然是reduced since VC10

看到一个库(无论是标准库还是其他库)触发的副本比您对您的类型预期的要多,这种情况并不少见。通常,您对此无能为力。

人们通常会做两件事来防止复制:

  1. 通过引用获取obj(或者在您的情况下,使用const ref,因为foo 不会修改obj)。这需要将 std::ref 与异步一起使用。
  2. obj 定义一个move constructor。这不会阻止构建和销毁临时对象,但它会让您有机会稍微优化流程。

请注意,在您仅保留一个 int 的对象的裸示例中,复制而不是移动或通过引用传递实际上可能更快。


通过引用将obj 传递到async 的示例:

void foo(const obj& a) {
    this_thread::sleep_for(chrono::milliseconds(500));
    cout << a.val << endl;
}

int main(int argc, int **args) {
    obj a(5);
    auto future = async(foo, std::cref(a));
    future.wait();

    return 0;
}

定义移动构造函数的示例:

class obj
{
public:
    /* ... */

    obj(obj&& a) : val(move(a.val)) {
        // It is good practice to 0 out the moved object to catch use-after-move bugs sooner.
        a.val = 0;
    }

    /* ... */
};

【讨论】:

  • 如此处所述stackoverflow.com/a/15040860/893819 我认为声明移动构造函数实际上可以阻止创建临时对象。
  • @PorkyBrain,答案不是这样。编译器也可以省略副本,它不受移动 cosntructor 存在的控制
  • 为什么 std::async 复制对象两次?
  • @DoehJohn 这就是 MSVC 对std::async 的实现方式。总会有至少一个副本或移动,因此async 可以拥有自己的副本。其他不是必需的,但只是async 在内部传递参数的一部分。由于 std::bind 不在内部使用,因此较新版本的 Visual Studio 更好,但仍有一些额外的动作。
【解决方案2】:

a 在绑定阶段被复制。为避免 a 的多个副本,请使用 move constructor 语义:

move ctor 添加到obj

class obj {
public:
    ...
    obj(obj&& other) {
        cout << "move obj" << endl;
        val = std::move(other.val);
    }
};

主要:

    obj a(5);
    auto future = async(foo, std::move(a));
    ...

这样,仍然会创建 5 个 obj 实例,但由于异步支持 movable 对象,相同的副本将在实例之间移动(对于重对象,这将比复制对象更重要)。所以现在的输出应该是:

new obj
move obj
move obj
move obj
move obj
delete obj
delete obj
delete obj
5
delete obj
delete obj

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-12
    • 2012-04-21
    • 2020-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-30
    相关资源
    最近更新 更多