c++ - 使用移动语义和支撑初始化对构造函数参数进行排序?

标签 c++ braced-init-list

SO 上有很多类似的问题,但我还没有找到一个能解决这个问题的问题。众所周知,std::move 是不动的。这有一个奇怪的优点,那就是删除潜在的“从移动的脚枪中读取”:如果我用 void f(std::vector<int>, std::size_t) 作为 std::vector<int> v 调用函数 f(std::move(v), v.size()) ,它将按某种顺序评估参数(我认为未指定?),但 std::move(v) 不会移动 v ,它只是转换为 std::vector<int>&& ,所以即使 std::move(v)v.size() 之前排序, v 也没有被移动,所以大小与首先排序的大小相同。对吗?

然后,我非常惊讶地看到这种逻辑在构造函数的花括号调用的特殊情况下崩溃了,但当我对构造函数使用括号时,并且仅当构造函数按值获取 vector 时,这种逻辑不会崩溃。那就是

#include <vector>
#include <fmt/core.h>

struct ByValue {
    std::vector<int> v;
    std::size_t x;
    ByValue(std::vector<int> v, std::size_t x) : v(std::move(v)), x(x) {}
};

struct ByRValueRef {
    std::vector<int> v;
    std::size_t x;
    ByRValueRef(std::vector<int>&& v, std::size_t x) : v(std::move(v)), x(x) {}
};

template <typename T>
void test(std::string_view name) {
    {
        auto v = std::vector<int>(42);
        // Construct with braces:
        auto s = T{std::move(v), v.size()};
        fmt::print("{}{{std::move(v), v.size()}} => s.x == {}\n", name, s.x);
    }
    {
        auto v = std::vector<int>(42);
        auto s = T(std::move(v), v.size());
        // Construct with parens:
        fmt::print("{}(std::move(v), v.size()) => s.x == {}\n", name, s.x);
    }
}

std::size_t getXValue(std::vector<int>, std::size_t x) { return x; }
std::size_t getXRValueRef(std::vector<int>&&, std::size_t x) { return x; }

int main() {
    test<ByValue>("ByValue");
    test<ByRValueRef>("ByRValueRef");
    {
        auto v = std::vector<int>(42);
        fmt::print("getXValue -> {}\n", getXValue(std::move(v), v.size()));
    }
    {
        auto v = std::vector<int>(42);
        fmt::print("getXRValueRef -> {}\n", getXRValueRef(std::move(v), v.size()));
    }
}

gcc 打印

ByValue{std::move(v), v.size()} => s.x == 0
ByValue(std::move(v), v.size()) => s.x == 42
ByRValueRef{std::move(v), v.size()} => s.x == 42
ByRValueRef(std::move(v), v.size()) => s.x == 42
getXValue -> 42
getXRValueRef -> 42

https://godbolt.org/z/nz3qx8nvK

但是 clang 不同意:

ByValue{std::move(v), v.size()} => s.x == 0
ByValue(std::move(v), v.size()) => s.x == 0
ByRValueRef{std::move(v), v.size()} => s.x == 42
ByRValueRef(std::move(v), v.size()) => s.x == 42
getXValue -> 0
getXRValueRef -> 42

https://godbolt.org/z/drbYzoovd

对于 gcc,唯一令人惊讶的情况是 ByValue{std::move(v), v.size()} => s.x == 0 。这里发生了什么?我假设 ByValue{...} 变成对 ByValue(std::vector<int> v, std::size_t x) 的“函数”(cunstructor)调用,我认为这意味着它会评估所有参数( std::move(v)v.size() ),然后调用该函数。

对于 clang,所有按值构造函数和函数调用都会在对 v 进行相应调用之前将 v.size() 置于移出(空)状态。

发生什么事了? gcc 是否从右到左计算(大括号除外),而 clang 总是从左到右计算,并且如果函数按值获取 vector ,则创建的参数是一个 vector ,所以它基本上是说 std::vector<int>(std::move(v)), v.size() ,按这个顺序排序?

最佳答案

so even if std::move(v) is sequenced before v.size(), v hasn't been moved from so the size is the same as if it were sequenced first. Right?

不,不对。函数参数的初始化也发生在调用者上下文中调用函数之前。由于您的示例使用按值 std::vector参数,而不是引用,移动构造将在调用者中发生,并且可能(或可能不会)发生在调用 v.size() 之前。 ,导致多种可能的结果,您可以在 GCC 和 Clang 的不同行为中观察到这些结果。

使用大括号改变了这一点,因为使用大括号的初始化(无论它是否解析为构造函数调用或聚合初始化)被指定为严格从左到右评估所有初始值设定项及其关联效果。因此,结果不再有任何变化。移动构造将在 v.size() 之前发生称呼。您可以在您的示例中看到这一点,GCC 和 Clang 都同意 0作为尺寸。

当您切换到引用参数时,您的推理变得正确,并且编译器同意您的 size() 值。 .

关于c++ - 使用移动语义和支撑初始化对构造函数参数进行排序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74125111/

相关文章:

C++ 首先发生什么——破坏函数的参数或处理函数的返回值?

c++ - 使用 thrust::transform 替换 for 循环

c - 将字符串文字与数组分配给 char

arrays - 将数组分配给C中的int

c++ - 具有大小的 vector 的类内初始化

c++ - 通过COM将自定义接口(interface)类型的SAFEARRAY返回给VB6

javascript - Node : What does `process.binding` mean?

c++ - 如何对 unique_ptr 的 vector 进行排序?

c++ - 递归嵌套初始值设定项列表采用变体