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

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

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

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

Есть ли механизм, чтобы сделать оба одновременно?

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
}

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

По своему дизайну 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).

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