只对右值参考的一些资源进行食人

Cannibalizing just some of the resources of an rvalue ref

本文关键字:资源 食人 值参 参考      更新时间:2023-10-16

我的意图如下:接收一个右值引用(即对我想拆散的对象的引用),移除它的一些资源,然后返回其他资源。我写了这个代码:

std::vector<int> dosomething(std::vector<int> && vec) {
    // do something with vec (e.g. consume SOME of its resources)
    return std::move(vec);
}

但我不确定是否:

  1. 这是实现我想要的正确方式吗
  2. 我应该使用std::forward吗?我不相信,因为这不是一个普遍的参考

这是实现我想要的正确方式吗?

好吧,让我们忽略这样一个事实,即vector<int>不是一个只带一些资源潜逃是合理的类型(它只有一个资源:分配)。我会假装你说的是某种实际上拥有多种资源的类型。

我认为这样的函数是本身的歪曲。

一个函数声明它将一个类型作为右值引用,这表明它将如何处理该值。这意味着它将直接或间接地将该对象移动到另一个该类型的对象中。它可以自己做,也可以打电话给其他愿意做的人。但在函数返回之前,会发生这种情况。

我们的想法是让这个代码的含义与它所说的完全一样:

SomeFunction(std::move(value));

看起来value正在被移动到SomeFunction中,就像您执行SomeType t = std::move(value);一样。在这两种情况下,value的状态都应该从移动。否则会使代码变得混乱。

一个函数想要任意修改一个参数,使其处于某种新状态,应该将该参数作为一个非常值左值引用,而不是右值引用。"modify"的确切含义取决于函数及其需要做的事情。如果你想用一些资源而不是其他资源潜逃,你可以用非常值引用来实现。您甚至可以从非常值引用的子对象中std::move。但是,您和您的用户应该了解函数调用后对象的状态。

当您通过右值引用获取参数时,这是"当我返回时,此对象将处于移出状态"的简写。请不要将右值引用误传为非常量左值引用。

我的意图如下:接收一个右值引用(即对我想拆散的对象的引用),删除它的一些资源,然后返回其他资源。

这没关系,但两者都可能更好:

  • 完全删除资源占位符,或

  • 用其他对象或替换删除的资源

  • 向呼叫者指示删除了哪些资源

因为如果你不这样做,那么用户可能会使用你拆散的其中一个移动项目。由于从对象(至少在STL中)移动的对象具有"有效但未定义"状态,因此使用这些拆包的对象将产生未定义的结果。

你不想那样。

这是实现我想要的正确方式吗?

看起来不错

我应该使用std::forward吗?我相信不会,因为这不是一个通用的参考

不,你不应该。你的推理是正确的。

仅供参考:"向用户指示删除了哪些资源"在标准库中有一个模型。看看std::remove_if——它实际上将"已删除"的项移动到序列的末尾,并返回一个迭代器,标记剩余项的end和"已删除的"项的开头。

这是我处理这个问题的一种方法。

目标:对向量中所有在词汇上大于"ccc"的字符串执行操作,使向量中的其余字符串按原始顺序

template<class T>
void do_something_with(T t)
{
    std::cout << "did something with " << t << std::endl;
}
template<class ForwardIt, class UnaryPredicate>
ForwardIt rotate_if(ForwardIt first, ForwardIt last, UnaryPredicate p)
{
    while(first != last)
    {
        auto n = std::next(first);
        if (p(*first))
        {
            if (n != last) {
                std::rotate(first, n, last);
                first = n;
                last = std::prev(last);
            }
            else {
                last = first;
                break;
            }
        }
        first = n;
    }
    return last;
}
template<class T, class Pred, class Model>
std::vector<T> do_something_if(std::vector<T>&& in, Pred pred, Model&& model)
{
    auto result = std::move(in);
    auto ibreak = rotate_if(std::begin(result),
                                 std::end(result),
                                 [&](auto& v) {
                                     auto match = pred(v, model);
                                     return match;
                                 });
    for (auto i = ibreak ; i != std::end(result) ; ++i) {
        do_something_with(std::move(*i));
    }
    result.erase(ibreak, std::end(result));
    return result;
}
void test()
{
    std::vector<std::string> words = {
        "yyy",
        "aaa",
        "zzz",
        "bbb",
    };
    auto filtered = do_something_if(std::move(words), std::greater<>(), "ccc");
    std::cout << "remaining items:n";
    for (auto& w : filtered) {
        std::cout << w << std::endl;
    }
}

预期输出:

did something with zzz
did something with yyy
remaining items:
aaa
bbb

在大多数情况下都会有更有效的解决方案,但这一解决方案避免了内存分配。

我假设std::vector<int>参数是一些更复杂类型的占位符,例如一些std::vector<Resource>。你不能移动int,你唯一能获得的其他资源就是向量本身的数据。

  1. 这是实现我想要的正确方式吗

是的。如果省略std::move,则vec将被视为左值,并将其复制到返回中。

您可以通过以下示例观察这种行为:

struct C
{
    C() {}
    C( C const& ) { std::cout << "copyn"; }
    C( C&& ) { std::cout << "moven"; }
};
C f( C&& c )
{
    return std::move( c ); // cast to rvalue reference, invokes the move ctor
}
C g( C&& c )
{
    return c; // does not cast to rvalue reference, will invoke the copy ctor
}
int main()
{
    f( C{} ); // calling with rvalue, the return causes the move constructor to be called.
    g( C{} ); // calling with rvalue, but no std::move means calling the copy constructor.
}
  1. 我应该使用std::forward吗?我不相信,因为这不是一个普遍的参考

没有。这不会有什么坏处,因为std::forward<T>有条件地强制转换(左值不会被转换为右值引用,右值会被转换)。然而,如果有更多的类型,它将基本上实现与std::move相同的功能。

根据经验,std::move与右值一起使用,std::forward<T>与通用引用一起使用。