Есть ли причины не расширять std :: set для добавления оператора индекса? - PullRequest
0 голосов
/ 09 сентября 2018

Я использую std::set для хранения уникальных экземпляров класса.std::set не имеет перегруженного оператора подписки, поэтому вы не можете, например, set[0].

Я нашел способ сделать это:

auto myClass = *std::next(set.begin(), index);

Однако я нашел егомонотонный копировать этот код снова и снова.Поэтому я решил, что было бы гораздо удобнее просто расширить std::set (class sset) и просто перегрузить оператор индекса в нем.

template <class T>

class sset: public std::set<T>
{
public:
    T operator[](const uint32_t i) const
    {
        if(i <= (this->size()-1))
            return *std::next(this->begin(), i);
        else
            throw std::out_of_range("Index is out of range");
    }
};

int main()
{
    auto myClass = set[0]; //works and no exception thrown

    return 0;
}

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

Есть ли какой-либо предопределенный недостаток или возможные будущие проблемы, которые можно увидеть при этом?

Ответы [ 3 ]

0 голосов
/ 09 сентября 2018

У std :: set обычно нет эффективного способа доступа к n-му элементу.Метод, который вы используете, будет начинаться с начала набора и продвигаться по одному элементу за раз, пока не дойдет до n-го.Это было бы очень медленно для больших наборов.

Если вам это нужно, то обязательно сделайте это, но помните о неэффективности.

0 голосов
/ 09 сентября 2018

Помимо уже упомянутых проблем эффективности, std::set (как и большинство других стандартных контейнеров) не предназначен для наследования - особенно, он не предоставляет виртуальный деструктор, поэтому следующее обязательно должно произойти:

std::set<MyType>* s = new sset<MyType>();
delete s;

Конечно, должно быть мало причин для создания набора таким образом, но проблема все еще остается ...

Если вы действительно, действительно нуждаетесь в n элементе и не хотите все время переписывать ваш пример кода, я бы предпочел иметь отдельную функцию для (по крайней мере) сомнительного наследования :

template <typename T>
T& at(std::set<T>& s, size_t index)
{
    if(i < s.size())
        return *std::next(s.begin(), index);
    throw std::out_of_range("index is out of range");
}
template <typename T>
T const& at(std::set<T> const& s, size_t index)
{
    if(i < s.size())
        return *std::next(s.begin(), index);
    throw std::out_of_range("index is out of range");
}

Никогда не используйте это в цикле for с итерацией от 0 до размера, используйте вместо этого итераторы или предпочтительно цикл на основе диапазона (который фактически отображается в цикл с использованием итераторов).

0 голосов
/ 09 сентября 2018

Индексирование никогда не должно превышать логарифмическое время, это то, что ожидается. Эта индексация является (по крайней мере) линейным временем. Это ужасно неэффективно. Если вы выполните все элементы в наборе, используя эту индексацию, вы получите общее квадратичное время. Это хорошая причина не делать этого.


Для показанного кода обратите внимание, что

if(i <= (this->size()-1)

плохо обрабатывает размер 0. В этом случае вы получаете переход без знака, так что условие составляет true. Разыменование конечного итератора становится неопределенным поведением.

...