Как я могу отсортировать вектор значений, на которые указывают, с помощью std::sort()?

Уже есть пост сортировка векторов указателей, но речь идет не о векторе указателей, а о векторе ссылающихся указателей.

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, поскольку она показывает мой подход к решению этой проблемы. Простая задача, которая оказывается довольно сложной. @ ответ Вирсавии указывает, что это невозможно. Таким образом, следующие части, которые первоначально считались презентацией моей попытки, теперь могут рассматриваться как причина, по которой Зачем это невозможно.


Моя идея состоит в том, чтобы создать оболочку класса указателя, чтобы предоставить мои собственные конструкторы и операторы присваивания. std::sort() ведет себя по-разному, если размер контейнера мал (<= 32 в моей системе), но в обоих случаях происходят назначения и перемещения — как показывает этот небольшой фрагмент из функции _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>& 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;
};

Как видите, есть также временные 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 сортируется, но не a, b, c

последний пункт имеет смысл, потому что, поскольку вызываются только функции перемещения, оператор присваивания копии 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 operator\n";
        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() для класса

но оба не работают.

  • оператор присваивания перемещения не может поменять местами значения, потому что начальный класс m_ptr из this-> всегда nullptr, и я бы предпочел не разыменовывать это.
  • std::sort() никогда не использует std::swap(), а просто std::move()s отовсюду. (как уже частично видно _Insertion_sort_unchecked).

Это похоже на проблему XY. Почему вы пытаетесь изменить переменные вне вектора? Это доказательство концепции или есть ситуация, в которой вам это действительно нужно?

NathanOliver 09.04.2019 15:48

спросите себя: если вы вырвете страницы из телефонной книги и перемешаете их, изменит ли это чей-то номер телефона?

463035818_is_not_a_number 09.04.2019 15:49

поведение вашего первого кода - это то, что можно было бы ожидать, а остальное - tl; dr

463035818_is_not_a_number 09.04.2019 15:50

@NathanOliver с этим я хочу создать функцию шаблона с переменным числом аргументов, которая сортирует аргументы. Я мог бы просто скопировать их в вектор, отсортировать вектор и переназначить, но мне было интересно, можно ли просто сделать это а-ля векторный указатель

Stack Danny 09.04.2019 15:51

уверен, что знаю это tl;dr, поэтому я сказал, что последние части могут быть бесполезными. Но я бы не хотел, чтобы вопрос был таким коротким, поэтому я представил свой подход к решению этого @ user463035818

Stack Danny 09.04.2019 15:52

я даже не нашел ту часть, где вы задаете вопрос или где вы объясняете проблему. Действительно неясно, чего вы хотите достичь или как уже существующий ответ на вопрос «Как я могу отсортировать вектор указателей?» не применять

463035818_is_not_a_number 09.04.2019 15:54

Лучшим заголовком было бы «Как сортировать указанные значения?». Сортировка указателей - это нечто другое (и это то, что вы уже делаете).

rustyx 09.04.2019 15:56

как я уже сказал, первый код соответствует ожиданиям, а затем вы пишете: «Если кто-то уже знает решение здесь, не стесняйтесь пропустить следующие части. Я пытался решить его с помощью довольно сложного обходного пути. "но в этот момент еще не ясно, что вы пытаетесь решить

463035818_is_not_a_number 09.04.2019 15:56

@user463035818 user463035818 Это было неясно, я отредактировал это, как указал rustyx. Я спрашиваю, как я могу отсортировать вектор указателей по значению.

Stack Danny 09.04.2019 15:59

это казалось таким простым, и я был удивлен, что просто не мог этого сделать, как бы я ни пытался. Это кажется суперсложным, и я не хочу делать такой TL, DR, но нет никакого способа объяснить это легко.

Stack Danny 09.04.2019 16:02

Поскольку ваше объяснение неверно, вы не хотите сортировать указатели по значению, это уже сделано, вы хотите std::sort изменять значения, на которые указывают указатели в std::vector. Т.е. вам нужны ссылки в вашем std::vector, а не указатели

Slava 09.04.2019 16:04

@Слава, но ссылки - это просто указатели? как буквально

Stack Danny 09.04.2019 16:04

Нет, они не. Но, к сожалению, у вас не может быть ссылок в std::vector, а std::reference_wrapper не работает так, чтобы решить вашу проблему.

Slava 09.04.2019 16:06

пожалуйста, измените название, потому что оно вводит в заблуждение

cprogrammer 09.04.2019 16:43

да @cprogrammer извините. Я имею в виду, что это было вектор указателей, но важно, чтобы эти указатели работали как ссылки. Хотя это было бы понятно, прочитав первый пример кода... но все же; вводящее в заблуждение название.

Stack Danny 09.04.2019 16:49

Сортировка массива случайно разбросанных данных (доступ к которым осуществляется через указатели) замедлит сортировку. Было бы быстрее сделать однократное копирование из разбросанных данных в стандартный массив или вектор, выполнить сортировку и однократное копирование обратно в разбросанные данные.

rcgldr 09.04.2019 17:42

@rcgldr Я никогда не думал об этом, я мог бы провести сравнительный анализ, чтобы понаблюдать. Хороший звонок.

Stack Danny 09.04.2019 17:43

@StackDanny — для теста создайте вектор указателей (pointer[i] = &value[i]) на вектор случайных значений. Отсортируйте вектор указателей в соответствии со значениями, используя сравнение лямбда. Затем назначьте новый набор случайных значений, используя теперь зашифрованные указатели, и выполните пользовательскую сортировку, которая сортирует значения, на которые ссылаются зашифрованные указатели. Размер массива или вектора должен быть значительно больше размера кеша, так как кеш скроет проблемы с произвольным доступом.

rcgldr 09.04.2019 17:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
18
533
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Для этого вам нужно свернуть собственную функцию сортировки.

Лямбда обратного вызова используется для оценки порядка, но вам нужно настроить часть, которая выполняет фактический обмен элементами: а стандартная библиотека C++ sort не поддерживает ваши действия.

К счастью, быстрая сортировка без каких-либо наворотов (таких как предварительная рандомизация) получается в несколько десятков строк, так что это не особенно обременительная задача.

Вы всегда резервируете временную метку ответа, чтобы люди, выполняющие реальную работу, не получили его первыми?

Slava 09.04.2019 16:08

@Слава: Ты что? Мне потребовалось 22 минуты, чтобы ответить на это.

Bathsheba 09.04.2019 16:08

Я бы ожидал, что кто-то скажет: «Для этого вам нужно будет свернуть свою собственную функцию сортировки». предоставить образец кода. И кто-то, вероятно, уже работает над этим, но вы сначала получите свой ответ, просто сказав, что вам нужно это сделать, не давая фактического ответа. Точно так же вы можете написать здесь «мой ответ будет здесь позже, я работаю над этим»

Slava 09.04.2019 16:10

@Слава: Если вы не считаете этот ответ полезным, пожалуйста, понизьте его. Мне ясно, что ОП знает, как кодировать, судя по качеству вопроса, поэтому моя работа - указать подход. Если вы ответите кодом, я обязательно проголосую.

Bathsheba 09.04.2019 16:11

Я обнаружил, что это демотивирует, когда вы работаете над ответом, но затем видите, как кто-то отвечает на него раньше вас, набрав «сделай это таким образом», а затем изменив свой ответ, чтобы получить фактический ответ.

Slava 09.04.2019 16:14

@ Вирсавия Я полагаю, я никогда не думал, что это просто невозможно. Я подумал, что должен быть какой-то странный способ. Я имею в виду, что я даже пытался сделать свой собственный std::move для класса, чтобы удалить ссылки && или что-то еще, но это сводится к неизбежной логике, что это невозможно.

Stack Danny 09.04.2019 16:15

@Слава: «а затем изменить свой ответ, чтобы получить реальный ответ» - довольно серьезное обвинение, если оно выполняется путем плагиата другого ответа. Если вы заметили это, то вы должны сообщить об этом модераторам. Если у вас есть ответ, то стучите!

Bathsheba 09.04.2019 16:15

@StackDanny: я думаю, это невозможно, поскольку никто не убедил комитет по стандартам C++ в том, что это может быть полезно.

Bathsheba 09.04.2019 16:16

Я не говорю о «плагиате», я говорю, что многие люди покупают себе время, сначала набрав краткий ответ, а затем работая над реальным. Я вижу, что это происходит очень часто здесь.

Slava 09.04.2019 16:18

@Слава: Что ж, это опасная игра, так как вы можете получить целую кучу отрицательных голосов на ранних черновиках. Ваш вступительный комментарий был странным, поскольку этот ответ пришел через 22 минуты после того, как был задан вопрос.

Bathsheba 09.04.2019 16:21

@ Вирсавия, в этом проблема. Я не совсем понимаю преимущества «ранних черновиков». Так много людей научились использовать или злоупотреблять системой, правильный ли это путь?

Slava 09.04.2019 16:23

@Слава: Вы должны поднять вопрос по этой теме на мета, если не дубликат. Ради интереса, где ты отвечаешь?

Bathsheba 09.04.2019 16:25

Просто сохраните копию исходного вектора указателей и скопируйте отсортированные значения:

            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

хорошо, если я сделаю это так, мне вообще не нужны указатели, так как я могу просто копировать - сортировать - копировать обратно, что я и прокомментировал, я бы предпочел не делать. Но учитывая, что моя первоначальная идея невозможна, я делаю либо это, либо делаю свой собственный сорт. Итак, в конце концов, это вопрос производительности и ремонтопригодности между ответом Батшебы и вашим.

Stack Danny 09.04.2019 16:28

Другие вопросы по теме