为什么此代码可能会禁用移动语义和复制省略?

Why may this code disable move semantics and copy elision?

提问人:o_oTurtle 提问时间:2/8/2023 更新时间:2/8/2023 访问量:110

问:

有时我们可能会像这样推迟完美的回报:

template<typename Func, typename... Args>
decltype(auto) call(Func f, Args&&... args)
{
    decltype(auto) ret{f(std::forward<Args>(args)...)};
    // ...
    return static_cast<decltype(ret)>(ret);
}

但在Jousttis的新书《C++ Move Semantics - The Complete Guide》中,他说下面的代码更好:

template<typename Func, typename... Args>
decltype(auto) call(Func f, Args&&... args)
{
    decltype(auto) ret{f(std::forward<Args>(args)...)};
    // ...
    if constexpr (std::is_rvalue_reference_v<decltype(ret)>) {
        return std::move(ret); // move xvalue returned by f() to the caller
    }
    else {
        return ret; // return the plain value or the lvalue reference
    }
}

因为第一段代码“可能会禁用移动语义和复制省略。对于普通 值,这就像在 return 语句中有一个不必要的 std::move()。这两种模式之间有什么区别?从我的角度来看,对于纯值,将只推断类型本身,所以它只是一个(即根本没有操作),返回的类型与声明的类型相同,因此可以进行复制省略。我有什么看错的地方吗?decltypestatic_cast<Type>(ret)

C++ 移动语义 省略

评论


答:

4赞 alagner 2/8/2023 #1

我不知道你有哪个版本的书,但我明确指出:

完美回归,但不必要的复制

该问题不会在引用中显现出来,但是当按值返回时,它就会显现出来。 请考虑以下代码:

#include <iostream>

struct S
{
    S() { std::cout << "constr\n";}
    S(const S& ) { std::cout << "copy constr\n"; }
    S(S&& ) { std::cout << "move constr\n"; }
};

S createSNoElide()
{   
    S s;
    return static_cast<decltype(s)>(s);
}

S createSElide()
{   
    S s;
    return s;
}

int main(int, char*[])
{
    std::cout << "Elision\n";
    S s1 = createSElide();
    std::cout << "No elision\n";
    S s2 = createSNoElide();
}

https://godbolt.org/z/YqG54rM9E

将强制使用复制构造函数。从标准上讲,这很可能是由于以下部分:createSNoElide()

这种复制/移动操作的省略称为复制省略,是 允许在以下情况下(可以合并到 消除多个副本):

在具有类返回类型的函数的 RETURN 语句中,当 expression 是具有自动存储功能的非易失性对象的名称 duration (函数参数或由 处理程序 ([except.handle])) 的异常声明,其中包含 与函数返回类型相同的类型(忽略 cv-qualification),则 通过直接构造对象,可以省略复制/移动操作 放入函数调用的返回对象

https://eel.is/c++draft/class.copy.elision

即,发生省略的唯一方法是返回局部变量的名称。Cast 只是一种不同类型的表达,它有效地防止了省略,无论这可能违反直觉。

另外,我应该特别感谢那篇帖子:https://stackoverflow.com/a/55491382/4885321 引导我到达标准中的确切位置。

评论

0赞 o_oTurtle 2/8/2023
哦,我明白了。对我来说,这将禁用复制省略,即使它实际上什么也没做,这真的有悖常理。谢谢!static_cast<Type>(arg)