使用类型擦除扩展结构体以避免大规模重构的问题咨询
问题与解答
问题背景
现有代码里广泛用Path(定义为using Path = std::vector<Point>;),Point只存数据,没额外功能。其中有个过滤方法:
Path filter(const Path& path) {... return filteredPath; }
现在需要给点加额外描述信息,打算做PointExt : Point这类扩展类型,但直接继承得把Path改成std::vector<std::unique_ptr<Point>>,要改大量代码。所以想搞类型擦除方案,既能保留原来加Point的用法,也能加PointExt:
Path path; path.push_back(Point()); path.push_back(PointExt());
把Point改成类型擦除结构、定义PointExt后,出现attempting to reference a deleted function错误,想问两个问题:
- 错在哪了?
- 这个解决方案合理吗?
相关代码
struct Point { template<typename T> Point(T&& p): object(std::make_shared<Model<T>>(std::forward<T>(p))) {} virtual PointType getType() const { return object->getType(); } struct Concept { virtual ~Concept() {} virtual PointType getType() const = 0; }; template<typename T> struct Model : Concept { Model(const T& t) : object(t) {} T object; PointType getType() const override { return object.getType(); } }; int x; int y; std::shared_ptr<Concept> object; }; struct PointExt : Point { PointType getType() const override { return PointType::PointExt ; } std::vector<Solution> solutions; }; struct PointExt2 : Point { PointType getType() const override { return PointType::PointExt2 ; } ... };
更新说明
已经找到错误原因:Point类缺默认构造函数,加Point() = default;后错误修复。
解答
1. 错误原因分析
你碰到的attempting to reference a deleted function错误,核心是自定义构造函数后,编译器不会自动生成默认构造函数。PointExt继承Point,创建PointExt对象时会先调用Point的默认构造函数初始化基类部分,但你的Point只定义了模板构造函数,默认构造函数被编译器自动删除,导致基类初始化失败,触发错误。加Point() = default;显式声明默认构造函数后,编译器会生成该函数,问题就解决了。
2. 解决方案合理性分析
这个类型擦除的思路有可取之处,但也有不少需要注意的问题:
- 优点:
- 不用大规模修改现有
Path的定义,保留了原有代码的兼容性,不用把容器改成智能指针类型,改动成本低。 - 能灵活扩展不同的
Point子类,在需要额外信息的地方通过getType()判断类型后转换,满足业务需求。
- 不用大规模修改现有
- 潜在问题:
- 类型转换风险:后续要拿
PointExt等子类的额外信息时,必须先通过getType()判断类型,再用dynamic_cast转换,一旦类型判断错或者转换失败,会引发运行时错误,增加维护成本。 - 数据冗余:你的
Point本身有int x; int y;成员,而类型擦除的Model又持有一个T对象(比如PointExt继承Point,会包含基类的x、y和自身的solutions),这会造成数据重复,浪费内存。 - 设计混淆:原本的
Point是数据存储类,现在被改成类型擦除的容器类,同时又当基类让其他类继承,职责变得模糊,不符合单一职责原则。
- 类型转换风险:后续要拿
优化建议
如果要继续用类型擦除方案,可以调整设计:
- 让
Point只做接口或数据载体,把类型擦除的逻辑抽离到单独的类(比如AnyPoint)里,Path改成std::vector<AnyPoint>,虽然要改部分现有代码,但改动范围比换成智能指针小。 - 删掉
Point里的x、y成员,让所有具体点类型(包括原Point)都实现包含x、y的接口,通过类型擦除的Concept提供访问x、y的方法,避免数据冗余。 - 如果现有代码改动限制很严,也可以保留当前方案,但要严格规范类型转换的使用,尽量把转换逻辑封装起来,别在多处直接用
dynamic_cast。
内容的提问来源于stack exchange,提问作者theateist
相关产品推荐
相关产品推荐

