含引用成员的Route类实现可复制与可移动的方案咨询
解决Route类引用成员导致的移动语义问题
首先,你遇到的问题很典型:C中类的引用成员会导致默认的移动构造和移动赋值运算符被删除,而std::sort在排序过程中需要移动容器元素(尤其是当元素较大时,移动比拷贝更高效),这就直接引发了报错。把引用改成const指针确实是个快速解决方案,但还有几个更安全、更符合C idiom的方案,我来逐一说明:
方案1:使用std::reference_wrapper替代原始引用
std::reference_wrapper是C++标准库提供的一个轻量级包装器,它可以封装一个引用,同时支持拷贝、移动和赋值操作——这正是原始引用缺失的特性。它既保留了引用“不能为空”的安全性,又解决了移动语义的问题。
修改后的Route类代码如下:
#include <functional> // 要包含这个头文件 class Route { public: Route(std::vector<int> locationIds, const ILocationSource& source) : locationIds_(std::move(locationIds)), locationSource_(std::ref(source)) {} double getRouteLength() const { // 使用.get()获取原始引用 const ILocationSource& source = locationSource_.get(); // 原来的计算逻辑不变 double length = 0.0; for (size_t i = 1; i < locationIds_.size(); ++i) { Location prev = source.getLocation(locationIds_[i-1]); Location curr = source.getLocation(locationIds_[i]); // 计算两点距离并累加,这里省略具体实现 } return length; } bool operator<(const Route& other) const { return getRouteLength() < other.getRouteLength(); } private: std::vector<int> locationIds_; std::reference_wrapper<const ILocationSource> locationSource_; };
这个方案的优势:
- 无需手动实现移动构造/赋值运算符,编译器会自动生成
- 比裸指针更安全,不会出现空指针问题(只要初始化时绑定的对象有效)
- 完全保留了对
ILocationSource的引用语义,没有额外的内存开销
方案2:使用智能指针管理ILocationSource
如果你的场景中需要共享ILocationSource实例,或者需要管理其生命周期,那么使用智能指针是更合适的选择。推荐用std::shared_ptr<const ILocationSource>,它支持移动语义,还能自动管理对象的生命周期,避免悬空指针。
修改后的Route类核心部分:
class Route { public: Route(std::vector<int> locationIds, std::shared_ptr<const ILocationSource> source) : locationIds_(std::move(locationIds)), locationSource_(std::move(source)) {} double getRouteLength() const { // 直接使用指针访问 Location prev = locationSource_->getLocation(locationIds_[i-1]); // ... 其他逻辑不变 } private: std::vector<int> locationIds_; std::shared_ptr<const ILocationSource> locationSource_; };
注意事项:
- 如果
ILocationSource的生命周期由外部完全控制,也可以考虑std::weak_ptr,但需要在使用前检查是否过期 - 智能指针会带来一点点额外的内存开销(引用计数),但安全性提升很多
方案3:手动实现移动构造(不推荐)
如果你坚持要保留原始引用成员,可以手动实现移动构造函数,但移动赋值运算符无法实现(因为引用一旦绑定就不能重新指向其他对象)。这种方式局限性很大,因为std::sort可能会用到移动赋值,所以不推荐:
class Route { public: // 手动实现移动构造 Route(Route&& other) noexcept : locationIds_(std::move(other.locationIds_)), locationSource_(other.locationSource_) {} // 移动赋值运算符必须删除,因为无法重新绑定引用 Route& operator=(Route&&) = delete; // ... 其他成员不变 };
这种方案只能解决部分场景的问题,而且容易引发误用,所以优先级低于前两种方案。
总结
- 如果你不需要管理
ILocationSource的生命周期,优先选std::reference_wrapper,它是最贴合你需求的轻量级方案 - 如果需要共享或管理生命周期,选
std::shared_ptr - 裸指针虽然可行,但安全性最差,除非你有非常特殊的理由,否则不建议使用
最后别忘了保证所有Route对象的生命周期都短于它们引用/指向的ILocationSource实例,避免悬空引用或指针的问题。
内容的提问来源于stack exchange,提问作者carlsb3rg
相关产品推荐
相关产品推荐

