я использую 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;
}
Я добился желаемого поведения, но мне пришло в голову, что, должно быть, была причина, по которой стандарт не включал подстрочный оператор. Конечно, не просто лень.
Есть ли какой-либо предопределенный недостаток или возможные будущие проблемы, которые можно увидеть при этом?
Индексирование никогда не должно быть больше логарифмического времени, это то, что ожидается. Эта индексация является (по крайней мере) линейным временем. Это ужасно неэффективно. Если вы выполните все элементы в наборе, используя эту индексацию, вы получите общее квадратичное время. Это хорошая причина не делать этого.
Обратите внимание, что для показанного кода
if(i <= (this->size()-1)
плохо обрабатывает размер 0 В этом случае вы получаете без подписи, чтобы условие true
, Разыменование конечного итератора в этом случае является неопределенным поведением.
У std :: set обычно нет эффективного способа доступа к n-му элементу. Метод, который вы используете, будет начинаться с начала набора и продвигаться по одному элементу за раз, пока не дойдет до n-го. Это было бы очень медленно для больших наборов.
Если вам это нужно, то непременно сделайте это, но помните о неэффективности.
Помимо вопросов эффективности, уже упомянутых, 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 до размера, используйте вместо этого итераторы или предпочтительно цикл на основе диапазона (который фактически отображается в цикл с использованием итераторов).