0

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;
}

我实现了预期的行为,但我突然想到标准不包含下标运算符一定是有原因的。当然不只是懒惰。

这样做是否有任何预先知道的缺点或可能的未来问题?

4

3 回答 3

10

索引不应超过对数时间,这是预期的。该索引是(至少)线性时间。这是非常低效的。如果您使用该索引遍历一组中的所有项目,您将获得二次总时间。这是一个很好的理由不这样做。


对于显示的代码,请注意

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

不能很好地处理大小 0。在这种情况下,您会得到无符号环绕,因此条件是true. 然后取消引用结束迭代器是未定义的行为。

于 2018-09-09T06:04:22.323 回答
4

std::set 通常没有有效的方法来访问第 n 个元素。您使用的方法将从集合的开头开始,一次推进一个元素,直到它到达第 n 个。对于大型集,这将非常慢。

如果您需要它,那么一定要这样做,但要注意效率低下。

于 2018-09-09T06:06:20.897 回答
0

除了已经提到的效率问题外,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");
}

永远不要在从 0 迭代到 size 的 for 循环中使用它,而是使用迭代器,或者最好使用基于范围的循环(它实际上映射到使用迭代器的循环)。

于 2018-09-09T06:47:44.053 回答