3

这个问题(下面的代码)std::async 和对象复制让我想到了复制/移动重型库实现。例如,这个制作 5 个副本。对策可能很棘手,例如在异步情况下,通过引用而不是值传递可能会导致并发问题。移动构造函数也不总是便宜的。

我的问题是,像找到一种通过引用传递的方法这样的对策真的可以解决一个不存在的问题吗?我假设大多数(如果不是全部)库函数最终都会被内联,它们通常只有一两行代码。我当然仍然会在输出中得到五个“删除 obj”,因为编译器必须遵循 as if 规则。如果我要通过不打印来消除析构函数的副作用,那么好像规则会更进一步并最终在发布版本中只有一个副本/移动吗?

在编写移动构造函数时我应该注意哪些事情,以免混淆编译器或无意引入副作用并阻止它进行优化?

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;
}
4

2 回答 2

5

数数会导致海森堡效应吗?

不,不是。如果您的意思是对事件的观察会改变事件本身,我认为在这种情况下这是一个错误的比喻。

风险在于您的观察会错过事件,就好像您从未编写过这些副作用一样。虽然,一般来说,编译器应该根据“as if”规则(1.9/1)考虑副作用,但有一些“as if”规则不适用的重要情况这种情况是在复制时/move 临时对象的省略可以由编译器执行。

C++11 标准的第 12.8/31 段精确描述了允许这样做的情况:

当满足某些条件时,允许实现省略类对象的复制/移动构造,即使对象的复制/移动构造函数和/或析构函数具有副作用。在这种情况下,实现将省略的复制/移动操作的源和目标简单地视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象本应被删除的较晚时间。没有优化就被破坏了。这种复制/移动操作的省略,称为复制省略,在以下情况下是允许的(可以结合起来消除多个副本):[...]

然后该段继续列出具体情况。这与回答问题无关,因此我省略了它们。你可以去查一下。

重要的是您的副作用可能会被跳过。如果 的实现std::async()执行了一些复制或移动,这取决于如何完成,即使你有一些打印输出,编译器也可能会忽略它们,或者更一般地说,即使你有一些副作用。

因此,您不应将复制构造函数、移动构造函数或析构函数中的副作用作为一种可移植的方式来计算在函数中执行了多少复制或移动:不同的编译器、不同的优化级别,当然还有不同的实现该功能可能会产生不同的结果。

于 2013-02-23T14:05:22.180 回答
3

只需要一个副本,这是因为您将左值传递给std::async,如果您的类型是可移动的,则所有其他的都可以移动,这不是因为您的用户定义的析构函数抑制了隐式定义的移动构造函数。async(foo, std::move(a))您可以通过传递一个右值将一个副本变成一个移动。

请记住,如果您看到五个析构函数,其中一个是a自身,一个是 的函数参数foo,因此剩下三个在内部由std::async. 使用 GCC std::async,只有两个,我不认为它可以用更少的东西来完成,但它也不应该真的需要更多。如果类型是可移动的,则所有内部“副本”都应该移动(除非您的实现async写得很糟糕)

无论如何,如果您使移动类型变得便宜,那么如果您有三步或十步并不重要。

正如对您的其他问题的回答中所解释的那样,您的析构函数中的副作用不会阻止复制省略,即使它消除了副作用也允许发生这种情况,因此回答:

as if 规则会更进一步,最终在发布版本中只有一个副本/移动吗?

是否,答案是:

在编写移动构造函数时我应该注意哪些事情,以免混淆编译器或无意引入副作用并阻止它进行优化?

也是号。副作用不妨碍移动。移动不是由优化器完成的,它是在您的代码从右值构造(或分配)对象时完成的。这不是优化,它受 C++ 语言的规则和语义支配。

在这段代码中:

A a;
A b = a;

编译器不能为 使用移动构造函数b,无论它的优化器多么聪明。它必须使用复制构造函数,这就是 C++ 的规则所说的。在这段代码中:

A a;
A b = std::move(a);

如果A有一个移动构造函数,编译器不能使用复制构造函数b,它必须使用移动构造函数,即使没有打开优化,这就是 C++ 的规则所说的。

于 2013-02-23T13:53:26.233 回答