C++中重载&&运算符失败及运行时访问冲突求助
问题背景
我在学习设计原则课程,尝试实现开闭原则:定义不同Specification(规格)类,交由Filter(过滤器)类校验实体是否合法。
现有代码结构
基类定义
template <typename T> struct Specification { virtual ~Specification() {} virtual bool is_satisfied(T* item) = 0; }; template <typename T> struct Filter { virtual std::vector<T*> filter(std::vector<T*> items, Specification<T>& spec) = 0; };
继承类定义
template <typename T> struct AndSpecification : Specification<T> { Specification<T>& first; Specification<T>& second; AndSpecification(Specification<T>&& first, Specification<T>&& second) : first(first), second(second) {} bool is_satisfied(T* item) override { return first.is_satisfied(item) && second.is_satisfied(item); } }; struct BetterFilter : Filter<Product> { std::vector<Product* > filter(std::vector<Product*> items, Specification<Product>& spec); }; struct ColorSpecification : Specification<Product> { Color color; ColorSpecification(Color color) :color(color) {} bool is_satisfied(Product* item); }; struct SizeSpecification : Specification<Product> { Size size; SizeSpecification(Size size) :size(size) {} bool is_satisfied(Product* item); };
重载运算符尝试
我尝试在全局作用域重载&&运算符:
template <typename T> AndSpecification<T> operator&&(Specification<T>&& one, Specification<T>&& two) { return AndSpecification<T>{one, two}; }
遇到的问题
编译错误
编译时出现如下错误:Error C2440 '<function-style-cast>': cannot convert from 'initializer list' to 'AndSpecification<Product>' DesignPrinciples C:\Users\ania\source\repos\DesignPrinciples\Main.cpp 24改为非模板版本仍无法编译。
运行时崩溃
添加std::move后可编译,但运行时出现读取访问冲突,触发位置为:AndSpecification<Product> spec = ColorSpecification(Color::green) && SizeSpecification(Size::large);
问题原因分析
编译错误原因
AndSpecification的构造函数接收右值引用参数,但初始化列表中却将其绑定到左值引用成员first和second。当用AndSpecification<T>{one, two}初始化时,one和two是函数参数(属于左值,尽管类型是右值引用),编译器无法完成这种类型转换,导致初始化失败。
运行时崩溃原因
即使添加std::move编译通过,ColorSpecification(Color::green)和SizeSpecification(Size::large)是临时对象,生命周期仅到表达式结束就会销毁。而AndSpecification的成员是引用,会指向这些已销毁的临时对象,后续调用is_satisfied时会访问已释放的内存,触发读取访问冲突。
解决方法
核心问题是引用绑定到临时对象导致的悬空引用,需要改为存储对象而非引用,或用智能指针管理生命周期,以下是两种可行方案:
方案1:使用智能指针管理多态对象
修改AndSpecification的成员为智能指针,转移临时对象的所有权,避免悬空引用:
#include <memory> template <typename T> struct AndSpecification : Specification<T> { std::unique_ptr<Specification<T>> first; std::unique_ptr<Specification<T>> second; AndSpecification(std::unique_ptr<Specification<T>>&& f, std::unique_ptr<Specification<T>>&& s) : first(std::move(f)), second(std::move(s)) {} bool is_satisfied(T* item) override { return first->is_satisfied(item) && second->is_satisfied(item); } };
调整运算符重载,适配智能指针的转移:
template <typename T> AndSpecification<T> operator&&(std::unique_ptr<Specification<T>>&& one, std::unique_ptr<Specification<T>>&& two) { return AndSpecification<T>{std::move(one), std::move(two)}; }
使用方式:
auto spec = std::make_unique<ColorSpecification>(Color::green) && std::make_unique<SizeSpecification>(Size::large);
方案2:存储值类型(适用于可安全拷贝的场景)
如果Specification子类支持移动/拷贝且无多态切片风险,可直接存储值:
template <typename T> struct AndSpecification : Specification<T> { Specification<T> first; Specification<T> second; AndSpecification(Specification<T>&& f, Specification<T>&& s) : first(std::move(f)), second(std::move(s)) {} bool is_satisfied(T* item) override { return first.is_satisfied(item) && second.is_satisfied(item); } };
调整运算符重载:
template <typename T> AndSpecification<T> operator&&(Specification<T>&& one, Specification<T>&& two) { return AndSpecification<T>{std::move(one), std::move(two)}; }
使用方式保持原有形式即可,临时对象会被移动到AndSpecification中:
AndSpecification<Product> spec = ColorSpecification(Color::green) && SizeSpecification(Size::large);
额外优化建议
- 将
Filter::filter的参数改为const Specification<T>&,因为校验操作不应该修改规格对象。 - 如果使用方案2,确保
Specification子类实现了正确的移动构造函数,避免切片问题。
内容的提问来源于stack exchange,提问作者Animesh Srivastawa

