使用std::shared_ptr时C++程序出现段错误的排查求助
你的代码出现段错误的核心原因是悬空指针导致的未定义行为,具体出在ctrVar函数的实现和lambda捕获逻辑上:
1. 临时对象的生命周期问题
ctrVar函数里的代码是:
return std::make_shared<ConstrainedVariable<I, O>>(ConstrainedVariable<I, O>(refVar, refCurve));
这里先创建了一个临时的ConstrainedVariable对象,再用这个临时对象初始化shared_ptr管理的堆对象(调用拷贝构造)。临时对象会在该行代码执行完毕后立即销毁。
2. Lambda捕获的this指向悬空
在ConstrainedVariable的构造函数中,你创建的lambda捕获了当前对象的this:
constraint = action([this](){this->applyConstraint();});
这个this指向的是临时对象的地址。当临时对象销毁后,lambda里的this就变成了悬空指针。后续当refVar触发更新调用这个lambda时,访问this->_refVar或this->_refCurve都会读取已释放的内存,进而出现段错误,调试中看到的nan就是内存乱读的结果。
3. 普通指针版本正常的原因
当你用栈上对象auto _c = ConstrainedVariable<...>(...)时,_c的生命周期和main函数一致,lambda捕获的this指向的是栈上的有效对象,所以不会出现悬空指针问题。
有两种直接的修复方式:
方式一:直接在make_shared中构造对象,避免临时对象
修改ctrVar函数,直接传递构造参数给make_shared,跳过临时对象的创建:
template<class I, class O> std::shared_ptr<ConstrainedVariable<I, O>> ctrVar(VariablePtr<I> refVar, Curve<I, O> refCurve){ return std::make_shared<ConstrainedVariable<I, O>>(std::move(refVar), std::move(refCurve)); };
这样make_shared会直接在堆上构造ConstrainedVariable对象,lambda捕获的this指向堆上的有效对象,生命周期由shared_ptr管理,不会悬空。
方式二:禁用拷贝构造,从源头避免错误
为了防止再写出类似的错误代码,可以禁用ConstrainedVariable的拷贝构造和赋值,强制使用移动语义:
template<class I, class O> class ConstrainedVariable : private Variable<O> { // ... 现有成员 ... // 禁用拷贝构造和拷贝赋值 ConstrainedVariable(const ConstrainedVariable&) = delete; ConstrainedVariable& operator=(const ConstrainedVariable&) = delete; // 允许移动构造和移动赋值 ConstrainedVariable(ConstrainedVariable&&) = default; ConstrainedVariable& operator=(ConstrainedVariable&&) = default; };
此时如果再写出创建临时对象拷贝的代码,编译器会直接报错,提前阻止错误发生。
修改ctrVar后运行代码,会得到正常输出:
0 0 1 0.841471
v->set(1)会触发ConstrainedVariable的applyConstraint,正确计算sin(1)的值,不会出现段错误。
内容的提问来源于stack exchange,提问作者Pedro Xavier

