Перемещайте объекты из набора в другой набор с другим функтором сравнения

В моей программе есть большое количество крупных объектов. В настоящее время они хранятся в std::set с функтором сравнения клиентов. Набор изначально пуст, и я продолжаю помещать в него объекты. Еще я периодически «съедаю» предметы с одного конца набора.

В нескольких (2-10) ключевых моментах во время выполнения моей программы я мог бы захотеть изменить сортировку этих объектов и продолжать размещать и использовать их в новом порядке. Обычно у меня есть три возможных порядка, и я постоянно переключаюсь между ними.

Мне бы хотелось, чтобы прикатывание происходило не копируя крупные объекты, а перемещая их.

Рассмотрим следующий пример:

#include <iostream>
#include <set>
#include <utility>

struct S {
    int a;
    
    S(int a) : a{a} { std::cout << "Constructor\n"; }
    S(const S& s) : a{s.a} { std::cout << "Copy constructor\n"; }
    S(S&& s) : a{std::exchange(s.a, 0)} { std::cout << "Move constructor\n"; }
    S& operator=(const S& s) { std::cout << "Copy assignment\n"; return *this = S(s); }
    S& operator=(S&& s) { std::cout << "Move assignment\n"; std::swap(a, s.a); return *this; }
};

int main() {
    auto order = [] (const S& s1, const S& s2) -> bool { return s1.a < s2.a; };
    auto inver = [] (const S& s1, const S& s2) -> bool { return s2.a < s1.a; };
    
    std::set<S, decltype(order)> set1;
    set1.emplace(1);
    set1.emplace(3);
    set1.emplace(2);
    
    for(auto&& s : set1) {
        std::cout << s.a << " ";
    }
    std::cout << "\n";
    
    std::set<S, decltype(inver)> set2{set1.begin(), set1.end()};
    
    for(auto&& s : set2) {
        std::cout << s.a << " ";
    }
    std::cout << "\n";
    
    return 0;
}

Который печатает следующий вывод:

Constructor
Constructor
Constructor
1 2 3 
Copy constructor
Copy constructor
Copy constructor
3 2 1

Можно ли использовать конструктор перемещения вместо конструктора копирования? Как? Если это невозможно, какая стратегия будет хорошей для решения моей проблемы? Должен ли я просто хранить свои объекты в структуре данных, которая не делает указатели недействительными и имеет прямой доступ в постоянное время (например, большой вектор, размер которого никогда не изменяется - или же, пожалуйста, предложите более подходящую структуру) и сортировать только их индексы?

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

Alberto Santini 30.07.2024 13:47
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
1
89
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Похоже, вы могли бы использовать set::extract для удаления элементов из первого набора и перегрузку дескриптора узла set::insert, чтобы добавить их во второй набор. Нет S не будет скопировано или перемещено во время этого процесса.

Так

#include <set>
#include <iostream>
#include <utility>

struct S {
    int a;
    
    S(int a) : a{a} { std::cout << "Constructor\n"; }
    S(const S& s) : a{s.a} { std::cout << "Copy constructor\n"; }
    S(S&& s) : a{std::exchange(s.a, 0)} { std::cout << "Move constructor\n"; }
    S& operator=(const S& s) { std::cout << "Copy assignment\n"; return *this = S(s); }
    S& operator=(S&& s) { std::cout << "Move assignment\n"; std::swap(a, s.a); return *this; }
};

int main() {
    auto order = [] (const S& s1, const S& s2) -> bool { return s1.a < s2.a; };
    auto inver = [] (const S& s1, const S& s2) -> bool { return s2.a < s1.a; };
    
    std::set<S, decltype(order)> set1;
    set1.emplace(1);
    set1.emplace(3);
    set1.emplace(2);
    
    std::set<S, decltype(inver)> set2;    
    // extract the first node
    auto h = set1.extract(set1.begin());
    // add to the second set
    set2.insert(std::move(h));

    for(auto&& s : set1) {
        std::cout << s.a << " ";
    }
    std::cout << "\n";
    for(auto&& s : set2) {
        std::cout << s.a << " ";
    }
    std::cout << "\n";
    
    return 0;
}

Выход

Constructor
Constructor
Constructor
2 3
1

Как видите, ничего не копировалось и не перемещалось.

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

Вы можете использовать std::set::extract (с std::move и std::set::emplace) следующим образом:

std::set<S, decltype(inver)> set2;
auto it = set1.begin();
while (it != set1.end())
{
    auto tmp = it;
    it++;   // increment `it` while it is still valid
    set2.emplace(std::move(set1.extract(tmp).value()));  // this will invalidate `tmp`, but we not longer need it for the loop
}

Обратите внимание, что мы используем итератор tmp для extract (который делает его недействительным) при увеличении it, который используется в цикле заранее, пока он все еще действителен.
Таким образом, вместо конструктора копирования будет вызываться конструктор перемещения S.

Live demo 1


Другой вариант — использовать std::set::insert с дескриптором узла (вместо std::set::emplace).
Это переместит весь узел, и даже конструктор перемещения S не будет вызван:

auto it = set1.begin();
while (it != set1.end())
{
    auto tmp = it;
    it++;   // increment `it` while it is still valid
    set2.insert(std::move(set1.extract(tmp))); // this will invalidate `tmp`, but we not longer need it for the loop
}

Live demo 2

Звучит здорово! Избегаю ли я также признания итераторов недействительными, если использую следующий код? for(auto it = set1.begin(); it != set1.end();) {set2.insert(std::move(set1.extract(it++)));}

Alberto Santini 30.07.2024 13:36

@AlbertoSantini, это немного сложно. Я не уверен насчет порядка оценки, поэтому на всякий случай предпочел использовать tmp. Но, как я уже сказал, я не уверен.

wohlstad 30.07.2024 13:43

@wohlstad Мне интересно, почему вы вызвали value() в дескрипторе узла, не проще ли и эффективнее просто передать дескриптор узла в insert?

john 30.07.2024 13:57

@Джон, я думаю, ты прав. Я был ориентирован на вызов конструктора перемещения, как первоначально просил ОП. Использование вставки с дескриптором узла позволит избежать даже вызова конструктора перемещения. Я добавлю это к своему ответу.

wohlstad 30.07.2024 13:59

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