创建引用向量的优雅方式

Elegant way to create a vector of reference

本文关键字:方式 引用 向量 创建      更新时间:2023-10-16

我有一个Foo 的向量

vector<Foo> inputs

Foo是一个在中有一些分数的结构

struct Foo {
...
float score
bool winner
}

现在我想按分数对输入进行排序,只将获胜者分配给前3名。但是我不想改变原始的输入向量。所以我想我需要创建一个参考向量,然后对其进行排序?创建引用向量合法吗?有没有一种优雅的方法可以做到这一点?

这里有两种不同的创建vector<Foo*>:的方法

vector<Foo*> foor; 
for (auto& x:inputs)
foor.push_back(&x);
vector<Foo*> foob(inputs.size(),nullptr); 
transform(inputs.begin(), inputs.end(), foob.begin(), [](auto&x) {return &x;}); 

然后,您可以使用标准算法对指针向量进行排序,而无需更改原始向量(如果需要):

// decreasing order according to score
sort(foob.begin(), foob.end(), [](Foo*a, Foo*b)->bool {return a->score>b->score;}); 

最后,您可以使用for_each_n()算法(如果是C++17)或简单地使用普通循环来更改前n个元素。

在线演示

给出的唯一示例代码是指针,只提到了更适合IMO的std::reference_wrapper,没有说明在这种情况下如何使用它。我想解决这个问题!


非拥有指针至少有3个缺点:

  • 视觉,从不得不在代码中使用&*->
  • 实用性:如果您只想引用一个对象,那么现在您就有了一个可以从其他指针中减去的东西(可能不相关)、递增/递减(如果不是const)、进行重载解析或转换等。–没有你想要的。我相信每个人都在嘲笑这件事,并说"我永远不会犯这样愚蠢的错误",但你知道,在足够长的时间内,会发生
  • 以及缺乏自我文档,因为它们没有所有权的固有语义或缺乏所有权

我通常更喜欢std::reference_wrapper,哪个

  • 清楚地自我记录了其纯粹的观察语义
  • 只能产生对对象的引用,因此没有任何类似指针的陷阱,并且
  • 通过隐式转换为真正引用的类型,避免了许多语法问题,从而最大限度地减少了可以调用转换的运算符噪声(传递到函数、初始化引用、范围-for等)……尽管干扰了auto–至少直到我们得到建议的CCD_ 11或operator auto–在其他情况下,或者如果您只是想避免这种不一致,则需要更详细的.get()。尽管如此,我认为这些褶皱既不比指针的褶皱更糟糕,也不可能是永久性的,因为有各种积极的提议来美化包装器/代理类型的使用

我建议使用那个或另一个词汇类,尤其是对于公开的数据。有observer_ptr之类的实验性建议,但同样,如果你真的不想要类似指针的行为,那么你应该使用一个为引用建模的包装器。。。我们已经有一个了。


所以。。。接受的答案中的代码可以这样重写(现在有了#includes和我对格式化的偏好):

#include <algorithm>
#include <functional>
#include <vector>
// ...
void
modify_top_n(std::vector<Foo>& v, int const n)
{
std::vector< std::reference_wrapper<Foo> > tmp{ v.begin(), v.end() };
std::nth_element( tmp.begin(), tmp.begin() + n, tmp.end(),
[](Foo const& f1, Foo const& f2){ return f1.score > f2.score; } );
std::for_each( tmp.begin(), tmp.begin() + n,
[](Foo& f){ f.winner = true; } );
}

这使用了范围构造函数从实际的Foos的范围构造reference_wrappers的范围,并在lambda参数列表中隐式转换为Foo&以避免必须执行reference_wrapper.get()(然后我们可以通过.而不是->进行混乱得多的直接成员访问)。

当然,这是可以概括的:分解为可重用辅助函数的主要候选者是为任意Foo构造vector< reference_wrapper<Foo> >,只给-Foo一对迭代器。但我们总是要把一些东西留给读者练习P

如果您真的不想修改原始向量,那么您必须将指针或索引的向量排序到原始向量中。要回答你的部分问题,不,没有办法制作一个参考向量,你不应该这样做

要查找前三个(或n)元素,甚至不必对整个向量进行排序。STL让你覆盖了std::nth_element(或者std::partial_sort,如果你关心顶部元素的顺序),你可以这样做:

void modify_top_n(std::vector<Foo> &v, int n) {
std::vector<Foo*> tmp(v.size());
std::transform(v.begin(), v.end(), tmp.begin(), [](Foo &f) { return &f; });
std::nth_element(tmp.begin(), tmp.begin() + n, tmp.end(),
[](const Foo* f1, const Foo *f2) { return f1->score > f2->score; });
std::for_each(tmp.begin(), tmp.begin() + n, [](Foo *f) {
f->winner = true;
});
}

假设向量至少具有n条目。我使用for_each只是因为当你有迭代器范围时更容易,你也可以使用for循环(或者Christophe提到的for_each_n,如果你有C++17)。

回答关于其面值的问题:

引用的向量(以及它们的内置数组)在C++中是不合法的。以下是阵列的标准措辞:

不得引用引用,不得引用数组,并且没有指向引用的指针。

对于向量,向量元素必须是可赋值的(而引用不是),这是禁止的。

要拥有间接对象的数组或向量,可以使用非拥有指针(std::vector<int*>),或者,如果需要非指针访问语法,可以使用包装器std::reference_wrapper

所以我想我需要创建一个引用向量,然后对其进行排序?创建引用向量合法吗?

不,不可能有引用向量。有std::reference_wrapper用于此目的,或者您可以使用裸指针。

除了Christophe展示的两种方法之外,还有一种方法是转换迭代器适配器,它可以用于使用std::partial_sort_copy将前3个指针/引用包装器排序为一个数组。

转换迭代器只是通过调用函数在赋值时转换输入来调整输出迭代器。不过,标准库中没有迭代器适配器,所以您需要自己实现一个,或者使用库。