如何使用 std::sort() 对指向值的向量进行排序?

How can can I sort a vector of pointed-to values using std::sort()?

本文关键字:向量 排序 std 何使用 sort      更新时间:2023-10-16

已经有指针的后排序向量,但这不是关于指针的向量,而是关于引用指针的向量。

3 个整数被放入一个std::vector<int*>根据指针后面的值进行排序。

#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int a = 3;
int b = 2;
int c = 1;
std::vector<int*> vec;
vec.emplace_back(&a);
vec.emplace_back(&b);
vec.emplace_back(&c);
std::sort(vec.begin(), vec.end(), [](const int* a, const int* b) {
return *a < *b;
});
std::cout << "vec = " << *vec[0] << ", " << *vec[1] << ", " << *vec[2] << 'n';
std::cout << "abc = " << a << ", " << b << ", " << c << 'n';
}

但是,似乎只有向量被排序,如输出所示:

vec = 1, 2, 3 
abc = 3, 2, 1 

我认为原因是std::sort()虽然比较正确,但只是分配地址而不是值。这是怎么回事?为什么我不能对指向值的向量进行排序?

下一部分是相当TL,DR,因为它显示了我解决这个问题的方法。一项简单的任务,表明自己相当令人沮丧地复杂。@Bathsheba的回答指出,这是不可能的。因此,接下来的部分,最初被认为是我的尝试的呈现,现在可能被认为是不可能的原因。


的想法是制作一个指针类包装器来提供我自己的构造器和赋值运算符。 如果容器的大小很小(在我的系统上<= 32),std::sort()的行为会有所不同,但在这两种情况下都会发生分配和移动 - 正如_Insertion_sort_unchecked(来自<algorithm>)函数中的这个小片段所示。

(_BidIt==std::vector<int*>::iterator_Iter_value_t<_BidIt>==int*)

_BidIt _Insertion_sort_unchecked(_BidIt _First, const _BidIt _Last, _Pr _Pred)
{   // insertion sort [_First, _Last), using _Pred
if (_First != _Last)
{
for (_BidIt _Next = _First; ++_Next != _Last; )
{   // order next element
_BidIt _Next1 = _Next;
_Iter_value_t<_BidIt> _Val = _STD move(*_Next);
if (_DEBUG_LT_PRED(_Pred, _Val, *_First))
{   // found new earliest element, move to front
_Move_backward_unchecked(_First, _Next, ++_Next1);
*_First = _STD move(_Val);

让我们创建一个类assignement_pointer,它的行为类似于指针,只是它分配值而不是地址。

template<typename T>
class assignement_pointer {
public:
assignement_pointer(T& value) {
this->m_ptr = &value;
std::cout << "<T>& constructorn";
}
assignement_pointer(const assignement_pointer& other) {
this->m_ptr = other.m_ptr;
std::cout << "copy constructorn";
}
assignement_pointer(assignement_pointer&& other) {
std::cout << "move assignement constructor >> into >> ";
*this = std::move(other);
}
assignement_pointer& operator=(const assignement_pointer& other) {
*this->m_ptr = *other.m_ptr;
std::cout << "copy assignement operatorn";
return *this;
}
assignement_pointer& operator=(assignement_pointer&& other) {
std::swap(this->m_ptr, other.m_ptr);
std::cout << "move assignement operatorn";
return *this;
}
T& operator*() {
return *this->m_ptr;
}
const T& operator*() const {
return *this->m_ptr;
}
private:
T* m_ptr;
};

如您所见,还有临时std::cout,以查看在主要经历std::sort()时调用了哪些构造函数/赋值运算符:

///...
std::vector<assignement_pointer<int>> vec;
vec.reserve(3);
vec.emplace_back(assignement_pointer(a));
vec.emplace_back(assignement_pointer(b));
vec.emplace_back(assignement_pointer(c));
std::cout << "nsort()n";
std::sort(vec.begin(), vec.end(), [](const assignement_pointer<int>& a, const assignement_pointer<int>& b) {
return *a < *b;
});
std::cout << "nvec = " << *vec[0] << ", " << *vec[1] << ", " << *vec[2] << 'n';
std::cout << "abc = " << a << ", " << b << ", " << c << 'n';

给出输出:

<T>& constructor
move assignement constructor >> into >> move assignement operator
<T>& constructor
move assignement constructor >> into >> move assignement operator
<T>& constructor
move assignement constructor >> into >> move assignement operator
sort()
move assignement constructor >> into >> move assignement operator
move assignement operator
move assignement operator
move assignement constructor >> into >> move assignement operator
move assignement operator
move assignement operator
move assignement operator
vec = 1, 2, 3
abc = 3, 2, 1
  • std::sort()调用仅移动函数。
  • 同样,vec被排序但不abc

最后一点是有道理的,因为因为只有移动函数被称为复制赋值运算符assignement_pointer& operator=(const assignement_pointer& other);(执行值赋值)。可以删除不必要的复制构造函数和赋值运算符:

template<typename T>
class assignement_pointer {
public:
assignement_pointer(T& value) {
this->m_ptr = &value;
}
assignement_pointer(const assignement_pointer& other) = delete;
assignement_pointer& operator=(const assignement_pointer& other) = delete;
assignement_pointer(assignement_pointer&& other) {
std::cout << "move assignement constructor >> into >> ";
*this = std::move(other);
}
assignement_pointer& operator=(assignement_pointer&& other) {
std::swap(this->m_ptr, other.m_ptr);
std::cout << "move assignement operatorn";
return *this;
}
T& operator*() {
return *this->m_ptr;
}
const T& operator*() const {
return *this->m_ptr;
}
private:
T* m_ptr;
};

现在std::sort()内部流程相当复杂,但最终归结为在像std::swap()这样的操作中失败:

int main() {
int a = 3;
int b = 2;
std::vector<assignement_pointer<int>> vec;
vec.reserve(2); //to avoid re-allocations
vec.emplace_back(assignement_pointer(a));
vec.emplace_back(assignement_pointer(b));
std::cout << "swap()n";
assignement_pointer<int> ptr_a{ a };
assignement_pointer<int> ptr_b{ b };
std::swap(ptr_a, ptr_b);
std::cout << "nptrs = " << *ptr_a << ", " << *ptr_b << 'n';
std::cout << "a, b = " << a << ", " << b << 'n';
}

如此输出所示:

move assignement constructor >> into >> move assignement operator
move assignement constructor >> into >> move assignement operator
swap()
move assignement constructor >> into >> move assignement operator
move assignement operator
move assignement operator
ptrs = 2, 3
a, b = 3, 2

就是只切换指针,而不是原始变量。std::swap基本上是

_Ty _Tmp = _STD move(_Left);
_Left = _STD move(_Right);
_Right = _STD move(_Tmp);

解释

move assignement constructor >> into >> move assignement operator
move assignement operator
move assignement operator

移动赋值运算符只是交换指针,因此创建临时变量不会执行任何操作。 我看到两种可能的解决方案:

  • 使移动分配运算符不是交换指针,而是值。
  • 为类实现我自己的swap()

但两者都不起作用。

  • 移动赋值运算符无法交换值,因为this->类的初始m_ptr始终是nullptr的,我宁愿不取消引用它。
  • std::sort()从不使用std::swap(),而只是从四面八方std::move()。(正如_Insertion_sort_unchecked已经部分看到的那样)。

您需要滚动自己的排序函数来执行此操作。

回调 lambda 用于评估排序,但您需要调整执行实际元素交换的部分:而C++标准库sort不支持您这样做。

幸运的是,没有任何花里胡哨的快速排序(例如预随机化)只有几十行,因此这不是一项特别繁重的任务。

只需保留指针的原始向量的副本,并将排序后的值复制到:

std::vector<int*> original = vec;  // Do this before the std::sort

然后在打印 a,b,c 后:

std::vector<int> xfer;
for (auto ptr : vec) {
xfer.push_back(*ptr);
}
auto it = std::begin(xfer);
for (auto ptr : original) {
*ptr = *it++;
}
std::cout << "abc = " << a << ", " << b << ", " << c << 'n';

输出:

abc = 1, 2, 3