缺少返回的局部变量的自动移动

时间:2021-05-22 20:13:12

标签: c++ gcc move

我遇到了一个问题,即在一组特定条件下,函数返回的局部变量被复制而不是移动。到目前为止,它似乎需要满足以下条件:

  • 返回的变量在函数中有一些用法。我假设否则整个复制/移动都被省略了。
  • 返回的类型使用了完美的转发式构造函数。从这个答案 (Usage of std::forward vs std::move) 中我了解到这种构造函数风格有一些不同的推导规则。
  • 8.0 之前在 gcc 中编译。在 clang 下编译(显然是 gcc 8.0+,感谢 PaulMcKenzie 的评论)产生了我期望的结果,但是我不能随意更改更大项目中使用的编译器。

这是一些最低限度的复制:

#include <iostream>
#include <type_traits>

// Test class to print copies vs. moves.
class Value
{
public:
    Value() : x(0) {}

    Value(Value&& other) : x(other.x)
    {
        std::cout << "value move" << std::endl;
    }

    Value(const Value& other) : x(other.x)
    {
        std::cout << "value copy" << std::endl;
    }

    int x;
};

// A container class using a separate lvalue and rvalue conversion constructor.
template<typename T>
class A
{
public:
    A(const T& v) : data_(v)
    {
        std::cout << "lvalue conversion" << std::endl;
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    A(T&& v) : data_(std::move(v))
    {
        std::cout << "rvalue conversion" << std::endl;
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    T data_;
};

// A container class using a single perfect forwarding constructor.
template<typename T>
class B
{
public:
    template <typename U>
    B(U&& v) : data_(std::forward<U>(v))
    {
        std::cout << "template conversion" << std::endl;
        std::cout << __PRETTY_FUNCTION__ << std::endl;
    }

    T data_;
};

// Get a Value rvalue.
Value get_v()
{
    Value v;
    v.x = 10;  // Without this things get ellided.
    return v;
}

// Get an A<Value> rvalue.
A<Value> get_a()
{
    Value v;
    v.x = 10;  // Without this things get ellided.
    return v;
}

// Get a B<Value> rvalue.
B<Value> get_b()
{
    Value v;
    v.x = 10;  // Without this things get ellided.
    return v;
}

int main()
{
    Value v = Value();

    std::cout << "--------\nA" << std::endl;
    std::cout << "lvalue" << std::endl;
    A<Value> a0(v);
    std::cout << a0.data_.x << std::endl;

    std::cout << "rvalue" << std::endl;
    A<Value> a1(get_v());
    std::cout << a1.data_.x << std::endl;

    std::cout << "get_a()" << std::endl;
    std::cout << get_a().data_.x << std::endl;

    std::cout << "--------\nB" << std::endl;
    std::cout << "lvalue" << std::endl;
    B<Value> b0(v);
    std::cout << b0.data_.x << std::endl;

    std::cout << "rvalue" << std::endl;
    B<Value> b1(get_v());
    std::cout << b1.data_.x << std::endl;

    std::cout << "get_b()" << std::endl;
    std::cout << get_b().data_.x << std::endl;

    return 0;
}

在 gcc 下这会产生:

--------
A
lvalue
value copy
lvalue conversion
A<T>::A(const T&) [with T = Value]
0
rvalue
value move
rvalue conversion
A<T>::A(T&&) [with T = Value]
10
get_a()
value move  <---- Works with separate constructors.
rvalue conversion
A<T>::A(T&&) [with T = Value]
10
--------
B
lvalue
value copy
template conversion
B<T>::B(U&&) [with U = Value&; T = Value]
0
rvalue
value move
template conversion
B<T>::B(U&&) [with U = Value; T = Value]
10
get_b()
value copy  <---- Not what I expect!
template conversion
B<T>::B(U&&) [with U = Value&; T = Value]
10

为了完整性,clang 给出:

--------
A
lvalue
value copy
lvalue conversion
A<Value>::A(const T &) [T = Value]
0
rvalue
value move
rvalue conversion
A<Value>::A(T &&) [T = Value]
10
get_a()
value move
rvalue conversion
A<Value>::A(T &&) [T = Value]
10
--------
B
lvalue
value copy
template conversion
B<Value>::B(U &&) [T = Value, U = Value &]
0
rvalue
value move
template conversion
B<Value>::B(U &&) [T = Value, U = Value]
10
get_b()
value move  <---- Like this!
template conversion
B<Value>::B(U &&) [T = Value, U = Value]
10

我有两个问题:

  • gcc 是否允许这种行为?
  • 有没有办法通过改变 A/B 的实现来强制移动行为?似乎任何时候你有一个模板化的函数参数被当作 && 它将触发完美转发的特殊规则,所以如果我尝试提供两个构造函数,如 A 示例中那样,一个采用 const U&,一个采用 U&&,它不会'只要他们有其他模板,就可以避免这个问题。

0 个答案:

没有答案
相关问题