Есть ли причины не расширять std :: set, чтобы добавить оператор индекса?

я использую 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;
}

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

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

0

Решение

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


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

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

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

10

Другие решения

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

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

4

Помимо вопросов эффективности, уже упомянутых, 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
По вопросам рекламы [email protected]