链式函数调用保存中间引用触发运行时错误的原因与解决问询
问题描述
原本可正常运行的链式仪器配置代码:
Setup().Thing().Channel(1,2).Channel(3,4).At("foo");
现在希望封装成接收通道对列表的配置函数,通过for循环调用setup.Channel(m,n),但保存Thing()或Channel()的返回引用时触发运行时错误:
Unhandled exception thrown: read access violation. this-> was 0xFFFFFFFFFFFFFFC7.
崩溃代码示例:
auto& setup = Setup().Thing(); setup.Channel(1,2) // 此处崩溃 .Channel(3,4).At("foo"); auto& setup = Setup().Thing().Channel(1,2).Channel(3,4); setup.At("foo"); // 此处崩溃
关键代码片段
Thing()函数实现
function::SetupComponentFunction<SetupResult>& Thing(int index, std::string alias) { auto type = std::make_shared<type::SetupType<T>>(); auto function = std::make_shared<function::SetupComponentFunction<T>>(index, alias); setType(type); type->setFunction(function); return *(function.get()); };
SetupComponentFunction<T>相关成员函数
SetupComponentFunction<T>& Channel(int cha, int chb) { memberFunction1(); // 调试步入时崩溃 ... return *this; } std::shared_ptr<T> At(const std::string& alias) { memberFunction2(alias); // 可步入此处 ... } // 基类中: #ifndef __INTELLISENSE__ std::shared_ptr<attribute::AttributeItem> memberFunction1() override; std::shared_ptr<T> memberFunction2(const std::string& alias) { return memberVar(alias).get(); // 调试步入时崩溃 } #endif boost::signals2::signal<std::shared_ptr<T>(const std::string&)> memberVar;
已知:memberFunction1()和memberVar的具体定义不可见;不保存引用时代码正常运行,但无法调试步入;保存引用则触发崩溃。
问题根源
核心问题是返回临时对象的引用,导致悬空引用:
- 在
Thing()函数中,function是局部std::shared_ptr,它管理的SetupComponentFunction<T>对象生命周期仅局限于Thing()函数内部。当Thing()执行完毕,function被销毁,指向的对象也会被释放(因为这是最后一个持有它的智能指针)。 - 用
auto& setup绑定的是已被销毁对象的引用,属于悬空引用。后续调用setup.Channel()或setup.At()时,访问的是已释放的内存,触发内存访问错误。 - 不保存引用时,整个链式调用在单条语句中完成:临时对象的生命周期延续到语句结束,所有调用都在对象有效期间执行,因此不会崩溃,但调试时因对象是临时的,无法正常步入。
解决方法
方法1:返回智能指针而非引用
修改Thing()和Channel()的返回类型为std::shared_ptr<SetupComponentFunction<T>>,由智能指针管理对象生命周期:
std::shared_ptr<function::SetupComponentFunction<SetupResult>> Thing(int index, std::string alias) { auto type = std::make_shared<type::SetupType<T>>(); auto function = std::make_shared<function::SetupComponentFunction<T>>(index, alias); setType(type); type->setFunction(function); return function; }; std::shared_ptr<SetupComponentFunction<T>> Channel(int cha, int chb) { memberFunction1(); ... return shared_from_this(); // 需要SetupComponentFunction继承std::enable_shared_from_this<T> }
调用时保存智能指针:
auto setup = Setup().Thing(); for (auto& [m,n] : channelPairs) { setup = setup->Channel(m,n); } setup->At("foo");
方法2:使用构建器模式封装链式调用
如果无法修改原有库的函数返回类型,可封装一个构建器类,在内部持有临时对象的生命周期:
class SetupBuilder { private: Setup setup_obj; std::shared_ptr<function::SetupComponentFunction<SetupResult>> component; public: SetupBuilder() : component(setup_obj.Thing()) {} SetupBuilder& addChannel(int m, int n) { component = component->Channel(m,n); return *this; } std::shared_ptr<T> finish(const std::string& alias) { return component->At(alias); } }; // 使用方式 SetupBuilder builder; for (auto& [m,n] : channelPairs) { builder.addChannel(m,n); } builder.finish("foo");
方法3:直接在链式调用中嵌入循环(C++11及以上)
如果仅需批量添加通道,可利用初始化列表或lambda结合链式调用,避免保存悬空引用:
auto addChannels = [](auto&& setup, const auto& pairs) -> decltype(auto) { for (auto& [m,n] : pairs) { setup = setup.Channel(m,n); } return setup; }; addChannels(Setup().Thing(), {{1,2}, {3,4}}).At("foo");
这里利用C++的引用折叠和临时对象生命周期延长机制,确保循环过程中对象始终有效。
内容的提问来源于stack exchange,提问作者H.v.M.
相关产品推荐
相关产品推荐

