Уже есть пост сортировка векторов указателей, но речь идет не о векторе указателей, а о векторе ссылающихся указателей.
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
).спросите себя: если вы вырвете страницы из телефонной книги и перемешаете их, изменит ли это чей-то номер телефона?
поведение вашего первого кода - это то, что можно было бы ожидать, а остальное - tl; dr
@NathanOliver с этим я хочу создать функцию шаблона с переменным числом аргументов, которая сортирует аргументы. Я мог бы просто скопировать их в вектор, отсортировать вектор и переназначить, но мне было интересно, можно ли просто сделать это а-ля векторный указатель
уверен, что знаю это tl;dr, поэтому я сказал, что последние части могут быть бесполезными. Но я бы не хотел, чтобы вопрос был таким коротким, поэтому я представил свой подход к решению этого @ user463035818
я даже не нашел ту часть, где вы задаете вопрос или где вы объясняете проблему. Действительно неясно, чего вы хотите достичь или как уже существующий ответ на вопрос «Как я могу отсортировать вектор указателей?» не применять
Лучшим заголовком было бы «Как сортировать указанные значения?». Сортировка указателей - это нечто другое (и это то, что вы уже делаете).
как я уже сказал, первый код соответствует ожиданиям, а затем вы пишете: «Если кто-то уже знает решение здесь, не стесняйтесь пропустить следующие части. Я пытался решить его с помощью довольно сложного обходного пути. "но в этот момент еще не ясно, что вы пытаетесь решить
@user463035818 user463035818 Это было неясно, я отредактировал это, как указал rustyx. Я спрашиваю, как я могу отсортировать вектор указателей по значению.
это казалось таким простым, и я был удивлен, что просто не мог этого сделать, как бы я ни пытался. Это кажется суперсложным, и я не хочу делать такой TL, DR, но нет никакого способа объяснить это легко.
Поскольку ваше объяснение неверно, вы не хотите сортировать указатели по значению, это уже сделано, вы хотите std::sort
изменять значения, на которые указывают указатели в std::vector
. Т.е. вам нужны ссылки в вашем std::vector
, а не указатели
@Слава, но ссылки - это просто указатели? как буквально
Нет, они не. Но, к сожалению, у вас не может быть ссылок в std::vector
, а std::reference_wrapper
не работает так, чтобы решить вашу проблему.
пожалуйста, измените название, потому что оно вводит в заблуждение
да @cprogrammer извините. Я имею в виду, что это было вектор указателей, но важно, чтобы эти указатели работали как ссылки. Хотя это было бы понятно, прочитав первый пример кода... но все же; вводящее в заблуждение название.
Сортировка массива случайно разбросанных данных (доступ к которым осуществляется через указатели) замедлит сортировку. Было бы быстрее сделать однократное копирование из разбросанных данных в стандартный массив или вектор, выполнить сортировку и однократное копирование обратно в разбросанные данные.
@rcgldr Я никогда не думал об этом, я мог бы провести сравнительный анализ, чтобы понаблюдать. Хороший звонок.
@StackDanny — для теста создайте вектор указателей (pointer[i] = &value[i]) на вектор случайных значений. Отсортируйте вектор указателей в соответствии со значениями, используя сравнение лямбда. Затем назначьте новый набор случайных значений, используя теперь зашифрованные указатели, и выполните пользовательскую сортировку, которая сортирует значения, на которые ссылаются зашифрованные указатели. Размер массива или вектора должен быть значительно больше размера кеша, так как кеш скроет проблемы с произвольным доступом.
Для этого вам нужно свернуть собственную функцию сортировки.
Лямбда обратного вызова используется для оценки порядка, но вам нужно настроить часть, которая выполняет фактический обмен элементами: а стандартная библиотека C++ sort
не поддерживает ваши действия.
К счастью, быстрая сортировка без каких-либо наворотов (таких как предварительная рандомизация) получается в несколько десятков строк, так что это не особенно обременительная задача.
Вы всегда резервируете временную метку ответа, чтобы люди, выполняющие реальную работу, не получили его первыми?
@Слава: Ты что? Мне потребовалось 22 минуты, чтобы ответить на это.
Я бы ожидал, что кто-то скажет: «Для этого вам нужно будет свернуть свою собственную функцию сортировки». предоставить образец кода. И кто-то, вероятно, уже работает над этим, но вы сначала получите свой ответ, просто сказав, что вам нужно это сделать, не давая фактического ответа. Точно так же вы можете написать здесь «мой ответ будет здесь позже, я работаю над этим»
@Слава: Если вы не считаете этот ответ полезным, пожалуйста, понизьте его. Мне ясно, что ОП знает, как кодировать, судя по качеству вопроса, поэтому моя работа - указать подход. Если вы ответите кодом, я обязательно проголосую.
Я обнаружил, что это демотивирует, когда вы работаете над ответом, но затем видите, как кто-то отвечает на него раньше вас, набрав «сделай это таким образом», а затем изменив свой ответ, чтобы получить фактический ответ.
@ Вирсавия Я полагаю, я никогда не думал, что это просто невозможно. Я подумал, что должен быть какой-то странный способ. Я имею в виду, что я даже пытался сделать свой собственный std::move для класса, чтобы удалить ссылки && или что-то еще, но это сводится к неизбежной логике, что это невозможно.
@Слава: «а затем изменить свой ответ, чтобы получить реальный ответ» - довольно серьезное обвинение, если оно выполняется путем плагиата другого ответа. Если вы заметили это, то вы должны сообщить об этом модераторам. Если у вас есть ответ, то стучите!
@StackDanny: я думаю, это невозможно, поскольку никто не убедил комитет по стандартам C++ в том, что это может быть полезно.
Я не говорю о «плагиате», я говорю, что многие люди покупают себе время, сначала набрав краткий ответ, а затем работая над реальным. Я вижу, что это происходит очень часто здесь.
@Слава: Что ж, это опасная игра, так как вы можете получить целую кучу отрицательных голосов на ранних черновиках. Ваш вступительный комментарий был странным, поскольку этот ответ пришел через 22 минуты после того, как был задан вопрос.
@ Вирсавия, в этом проблема. Я не совсем понимаю преимущества «ранних черновиков». Так много людей научились использовать или злоупотреблять системой, правильный ли это путь?
@Слава: Вы должны поднять вопрос по этой теме на мета, если не дубликат. Ради интереса, где ты отвечаешь?
Просто сохраните копию исходного вектора указателей и скопируйте отсортированные значения:
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
хорошо, если я сделаю это так, мне вообще не нужны указатели, так как я могу просто копировать - сортировать - копировать обратно, что я и прокомментировал, я бы предпочел не делать. Но учитывая, что моя первоначальная идея невозможна, я делаю либо это, либо делаю свой собственный сорт. Итак, в конце концов, это вопрос производительности и ремонтопригодности между ответом Батшебы и вашим.
Это похоже на проблему XY. Почему вы пытаетесь изменить переменные вне вектора? Это доказательство концепции или есть ситуация, в которой вам это действительно нужно?