在trivially-copyable类型上进行防御性地应用std::move是不可取的吗?

7

想象一下我们有一个平凡可复制的类型:

struct Trivial
{
    float A{};
    int B{};
}

这将被构建并存储在一个std::vector中:

class ClientCode
{
    std::vector<Trivial> storage{};
    ...

    void some_function()
    {
        ...
        Trivial t{};
        fill_trivial_from_some_api(t, other_args);

        storage.push_back(std::move(t));  // Redundant std::move.
        ...
    }
}

通常情况下,这是一个毫无意义的操作,因为对象将被复制。

然而,保留std::move调用的一个优点是,如果Trivial类型不再具有平凡的可复制性,客户端代码将不会默默执行额外的复制操作,而是执行更适当的移动操作。(在我的场景中,这种情况是很可能发生的,因为平凡类型用于管理外部资源。)

所以我的问题是,在应用冗余的std::move时是否存在任何技术缺陷?


你为什么要编写未来的代码,而不是现在所需的代码呢? - default
1
一个类似的困境(尽管不完全相同)可能是,如果 some_function 将来会在多线程架构中使用,那么你是否应该提前为存储添加 lock_guard? - default
1
我认为如果你这样做,不是因为防御性。如果调用者不会使用该值,则使用std::move可以明确地显示它。 - llllllllll
3
当涉及到泛型类型时,通常会出现一些技术上的缺点,主要表现为更多的代码,这使得阅读、推理以及编译器和分析器需要完成更多的工作。然而,这并不能回答你的实际问题。我认为在一般情况下你应该使用std::move,对于平凡类型的特殊情况,一般情况的代码仍然可以正确运行,所以你应该坚持使用它。但这只是我的观点,这是Stack Overflow不喜欢的。 - nwp
@Default 默认有效点。 - Disenchanted
@nwp 这基本上也是我的看法。我并没有过多考虑类型的实际作用,而是使用了 std::move,因为这正是我想要对该对象进行的操作。我非常希望不要让我的代码过于专业化,但有时可能会出现棘手的编译器优化问题。 - Disenchanted
1个回答

9
然而,保留 std::move 调用的优点在于,如果 Trivial 类型不再是平凡可复制的,则客户端代码将不会悄悄执行额外的复制操作,而会执行更适当的移动操作。
这是正确的,并且这是您应该考虑的事情。
因袭应用多余的 std::move 是否存在技术上的缺陷呢?这取决于移动对象在哪里被消耗。在 push_back 的情况下一切都很好,因为 push_back 既有 const T& 又有 T&& 重载,这些重载行为直观。
想象另一个函数有一个与 const T& 完全不同行为的 T&& 重载:使用 std::move 将改变代码的语义。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接