Как лучше организовать функцию

Есть такой код:

template<class __T>
class IdContainer
{
    std::vector<__T> unorderedIds_;

public:
    __T getNext() {
        auto _it = unorderedIds_.begin();

        if (_it != unorderedIds_.end()) {
            __T _id = *_it;
            unorderedIds_.erase(_it);
            return _id;
        }

        throw std::runtime_error("---");
    };
}

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

bool getNext(__T& id) {
    auto _it = unorderedIds_.begin();

    if (_it != unorderedIds_.end()) {
        id = *_it;
        unorderedIds_.erase(_it);
        return true;
    }

    return false;
};

Еще я вижу вариант с возвратом указателя:

__T* getNext() {
    return _id;
    or
    return nullptr;
}

но это прям перебор, выделять какой-нибудь int в куче.

Может есть и другие варианты которые я упускаю. Подскажите пожалуйста как лучше написать эту функцию. Ее идея в том что она должна выдать нам первый id иначе дать понять что id закончились.


Ответы (2 шт):

Автор решения: gbg

У вас есть целых 4 (вместе с optional) варианта решения данной проблемы, и все 4 варианта - хорошие.

Выбор конкретного варианта зависит от большого количества факторов:

  1. От соглашений, принятых на проекте

  2. От смысла выполняемой операции - обычно, исключение кидается, когда происходит ИСКЛЮЧИТЕЛЬНАЯ СИТУАЦИЯ, а программисты стараются не пускать обычную логику через исключения. Но это скорее вопрос соглашений, принятых на проекте.

  3. Какие лучше выбрать соглашения для проекта?

  • Если вы учитесь, вы должны знать и понимать все варианты

  • Если вы начинаете новый проект в составе команды, вы будете принимать совместное решение.

  • Если решение должны принимать лично вы, ориентируйтесь на свой опыт. Если опыта нет - у вас учебный проект, см. пункт выше (вам следует изучить все варианты).

→ Ссылка
Автор решения: Arty

Если у вас нет возможности перейти на новый компилятор и стандарт C++17, то вполне можно закодировать аналог std::optional, это просто, вот для примера я попробовал закодировать подобный класс:

Попробовать онлайн!

#include <utility>
#include <stdexcept>

template <typename T>
class Optional {
public:
    Optional() {}
    ~Optional() { Destruct(); }
    template <typename Q>
    Optional(Q && v) { Construct(std::forward<Q>(v)); }
    template <typename Q>
    Optional & operator = (Q && v) { return Construct(std::forward<Q>(v)); }
    Optional(Optional const & other) { Replace(other.has_val, other.val); }
    Optional & operator = (Optional const & other) {
        return Replace(other.has_val, other.val);
    }
    Optional(Optional && other) { Replace(other.has_val, std::move(other.val)); }
    Optional & operator = (Optional && other) {
        return Replace(other.has_val, std::move(other.val));
    }
    bool has_value() const { return has_val; }
    operator bool() const { return has_val; }
    T & value() {
        if (!has_val)
            throw std::runtime_error("Optional doesn't contain value!");
        return val;
    }
    T const & value() const { return const_cast<Optional&>(*this).value(); }
    T & operator * () { return val; }
    T const & operator * () const { return val; }
    T * operator -> () { return &val; }
    T const * operator -> () const { return &val; }
    void reset() { Destruct(); }
    template <typename ... Args>
    T & emplace(Args && ... args) {
        Construct(std::forward<Args>(args)...);
        return val;
    }
private:
    template <typename ... Args>
    Optional & Replace(bool has, Args && ... args) {
        return has ? Construct(std::forward<Args>(args)...) : Destruct();
    }
    template <typename ... Args>
    Optional & Construct(Args && ... args) {
        Destruct();
        new (&val) T(std::forward<Args>(args)...);
        has_val = true;
        return *this;
    }
    Optional & Destruct() {
        if (has_val) {
            val.~T();
            has_val = false;
        }
        return *this;
    }
    
    bool has_val = false;
    struct Dummy {};
    union {
        Dummy dummy;
        T val;
    };
};

А так вообще я думаю что просто возвращать bool из функции вида bool getNext(__T& id) как вы предложили это вполне подходящий и простой вариант.

Варианты с возвращением указателя или исключением тоже рабочие, но если это высокопроизводительный участок кода, то лучше их не использовать, они сильно замедлят код. bool вариант при этом не испортит производительности.

→ Ссылка