有什么理由不扩展 std::set 来添加下标运算符吗?
Are there any reasons not to extend std::set to add a subscript operator?
我正在使用 std::set
来存储 class 的唯一实例。 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");
}
永远不要在从 0 到 size 的 for 循环中使用它,而是使用迭代器,或者最好使用基于范围的循环(实际上映射到使用迭代器的循环)。
我正在使用 std::set
来存储 class 的唯一实例。 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");
}
永远不要在从 0 到 size 的 for 循环中使用它,而是使用迭代器,或者最好使用基于范围的循环(实际上映射到使用迭代器的循环)。