为std::span扩展仿std::string_view的substr成员函数,请教实现正确性
你的实现存在不安全的地方,虽然当前能编译运行,但static_cast的使用有未定义行为风险,下面具体分析并给出修正方案:
1. substr函数的错误点
你在substr里用static_cast<Span<T>&>(ret)转换std::span<T>对象为Span<T>引用,这属于向下转型(基类转派生类),但ret本身并不是Span<T>的实例,这种转型是未定义行为——当前编译器没报错只是巧合,一旦后续Span类添加成员变量,这种转型会直接访问非法内存,引发崩溃或奇怪的运行问题。
正确的写法应该利用你已经继承的基类构造函数,直接把subspan返回的std::span<T>转换成Span<T>:
constexpr Span substr(std::size_t pos, std::size_t count) { return Span(this->subspan(pos, count)); }
因为你已经通过using std::span<T>::span;继承了std::span的所有构造函数,而std::span支持用另一个std::span来构造,所以这种写法安全且符合C++规范。
2. remove_prefix函数的简化
你写的remove_prefix里的static_cast完全没必要,派生类可以直接访问基类的成员,简化后代码更清晰:
constexpr void remove_prefix(std::size_t n) { *this = this->subspan(n); }
派生类对象本身可以直接被赋值为基类的std::span对象,不需要额外转成基类引用。
3. 继承std::span的注意事项
std::span是非多态类(没有虚函数表),继承它来扩展成员函数是可行的,但要注意:
- 不要用
std::span<T>*或std::span<T>&指向Span<T>对象后执行删除操作(不过span是轻量级视图对象,一般不会这么用) - 所有扩展的成员函数都要基于基类的合法操作实现,你的
remove_prefix和substr都是用subspan实现,这一点是安全的。
4. 统一接口的替代方案
如果你只是想让std::span和std::string_view拥有统一的substr接口,也可以不用继承,而是写全局模板重载函数:
#include <span> #include <string_view> template<typename T> constexpr auto substr(const std::span<T>& s, std::size_t pos, std::size_t count) { return s.subspan(pos, count); } constexpr std::string_view substr(const std::string_view& sv, std::size_t pos, std::size_t count) { return sv.substr(pos, count); }
旧代码里的sv.substr(pos, count)改成substr(sv, pos, count),就能同时支持两种类型,避免了继承带来的潜在问题。
内容的提问来源于stack exchange,提问作者ollydbg

