Можно ли одновременно удалить и получить копию объекта из C++ std:: vector или std:: deque?


В Java класс Deque имеет методы удаления Для концов, которые фактически возвращают возвращаемый элемент. В C++ кажется, что единственный способ добиться такого же поведения - это сначала явно скопировать элемент, а затем вставить его.

std::deque<int> myDeque;
myDeque.push_back(5);

int element = myDeque.back();
myDeque.pop_back();

Существует ли механизм, позволяющий делать и то и другое одновременно?

3 3

3 ответа:

Вы можете написать свой собственный шаблон функции-оболочки:

// Precondition: !container.empty()
// Exception safety: If there is an exception during the construction of val,
//                   the container is not changed.
//                   If there is an exception during the return of the value,
//                   the value is lost.
template <typename C>
auto back_popper(C & container) -> decltype(container.back())
{
    auto val(std::move(container.back()));
    container.pop_back();
    return val;
}

Использование:

auto element = back_popper(myDeque);
Вы не можете обойти фундаментальную проблему, которая мотивирует разделение back() и pop_back() в первую очередь, которая заключается в том, что конструктор копирования или перемещения элемента может создавать исключения и что вы можете потерять выскочивший элемент, когда это произойдет. Вы могли бы смягчить его в каждом конкретном случае, вернув не бросающий объект, например unique_ptr, который обменял бы риск потери элемента на динамическое распределение, но очевидно, что это личный выбор, который вы должны сделать, который стандарт не делает для вас.

Например:

// Guarantees that you either get the last element or that the container
// is not changed.
//
template <typename C>
auto expensive_but_lossless_popper(C & container)
-> typename std::unique_ptr<decltype(container.back())>
{
    using T = decltype(container.back());

    std::unique_ptr<T> p(new T(std::move(container.back())));
    container.pop_back();
    return p;                // noexcept-guaranteed
}

Edit: я добавил std::move вокруг back() вызовов, как предложил @Simple. Это законно, так как перемещенный элемент больше не нужен, и многие классы реального мира поставляются с конструкторами noexcept move, так что это охватывает большое количество случаев, и обходной путь "без потерь" предлагает преимущество только для небольшого числа "странные" типы, которые не имеют никаких особых движений.

По своей конструкции C++ не предлагает такого механизма из коробки, чтобы обеспечить безопасность исключений:

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

Если вы не хотите потерять безопасность исключений, вы можете ограничить этот метод с помощью SFINAE:

// 
// Precondition: !container.empty()
//               
template <typename C>
auto back_popper(C & container)
-> typename std::enable_if<std::is_nothrow_move_constructible<
                             decltype(container.back())>::value,
                           decltype(container.back())>::type
{
    auto val = std::move(container.back());
    container.pop_back();
    return val;
}

Или static_assert (разберитесь сами). (Примечание: отредактировано, чтобы избежать каких-либо копий в случае, если есть конструктор перемещения, следуйте комментарию Simple.)