是否有任何理由不扩展std :: set以添加下标运算符?

时间:2018-09-09 05:58:57

标签: c++ class c++11 stdset extending-classes

我正在使用std::set存储类的唯一实例。 std::set没有重载的下标运算符,因此您不能执行set[0]

我找到了一种方法:

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

但是,我发现一遍又一遍地复制该代码是单调的。因此,我认为只扩展std::setclass 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 :(得分:10)

建立索引的时间不得超过对数时间,这是所期望的。该索引是(至少)线性时间。那是非常低效的。如果使用该索引遍历集合中的所有项目,则得到的总时间为平方。这是不这样做的很好理由。


对于显示的代码,请注意

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

不能很好地处理大小0。在这种情况下,您将获得无符号环绕,因此条件为true。这样,取消引用最终迭代器就是未定义行为。

答案 1 :(得分:4)

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

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

答案 2 :(得分: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循环中使用它,而应使用迭代器,或者最好使用基于范围的循环(实际上是使用迭代器映射到循环)。