简体   繁体   中英

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

There is already the post sorting vector of pointers but this is not about a vector of pointers but rather about a vector of referencing pointers.

3 integer are put into a std::vector<int*> which is than sorted according to the values behind the pointers.

#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';
}

however, it seems that only the vector was sorted as the output shows:

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

I think the reason is that std::sort() , while comparing correctly, simply assigns adresses instead of the values. What is wrong here? Why can't I sort this vector of pointed-to values?

The next part is rather TL,DR as it shows my approach to solve this. An easy task that reveals itself to be rather frustratingly complicated. @Bathsheba's answer points out that it is not possible . So the next parts, initially considered to be the presentation of my attempt, might now be considered to be the reason why it is not possible.


My idea is to make a pointer class wrapper to provide my own contructors and assignement operators. std::sort() behaves differently if the size of the container is small ( <= 32 on my system) but in both cases there are assignements and moves happening - as this small snippet from the _Insertion_sort_unchecked (from <algorithm> ) function shows.

( _BidIt == std::vector<int*>::iterator and _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);

Let's make a class assignement_pointer that behaves like a pointer except that it assigns the values instead of the adresses.

template<typename T>
class assignement_pointer {
public:
    assignement_pointer(T& value) {
        this->m_ptr = &value;
        std::cout << "<T>& constructor\n";
    }
    assignement_pointer(const assignement_pointer& other) {
        this->m_ptr = other.m_ptr;
        std::cout << "copy constructor\n";
    }
    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 operator\n";
        return *this;
    }
    assignement_pointer& operator=(assignement_pointer&& other) {
        std::swap(this->m_ptr, other.m_ptr);
        std::cout << "move assignement operator\n";
        return *this;
    }
    T& operator*() {
        return *this->m_ptr;
    }
    const T& operator*() const {
        return *this->m_ptr;
    }
private:
    T* m_ptr;
};

As you can see there are also temporary std::cout 's to see which constructors / assignement operators were called while going through std::sort() in the main:

    ///...
    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';

giving the output:

<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() calls only move functions.
  • again, vec is sorted but not a , b , c

the last point makes sense, because since only move functions are called the copy assignement operator assignement_pointer& operator=(const assignement_pointer& other); (which does the value assignement) is never called. The unnecessary copy constructor and assignement operator can be removed:

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 operator\n";
        return *this;
    }
    T& operator*() {
        return *this->m_ptr;
    }
    const T& operator*() const {
        return *this->m_ptr;
    }
private:
    T* m_ptr;
};

Now std::sort() inner processes are rather complicated but in the end it comes down to failing at an operation like 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';
}

and as this output shows:

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

it's that sitation that only the pointers are switched but not the original variables. std::swap is basically

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

explaining the

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

the move assignement operator simply swaps the pointers so creating a temporary variable doesn't do anything. I see two possible solutions to this:

  • make the move assignement operator not swap pointers but rather values.
  • implement my own swap() for the class

but both don't work.

  • the move assignement operator can't swap values because the initial m_ptr from this-> class is always nullptr and I would rather not dereference this.
  • std::sort() never uses std::swap() but instead just std::move() s from all over the place. (as already partially seen by _Insertion_sort_unchecked ).

You'll need to roll your own sort function to do this.

The callback lambda is used to evaluate the ordering, but you need to tweak the part that does the actual exchange of elements: and the C++ standard library sort does not support your doing that.

Fortunately a quick sort without any bells and whistles (such as pre-randomisation) comes out in a few tens of lines, so this is not a particularly onerous task to complete.

Just retain a copy of the original vector of pointers and copy the sorted values over:

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

Then after you print 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';

Output:

abc = 1, 2, 3

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM