记一次因对象构造顺序引发的踩内存问题
背景与现象
template<typename T>
struct range_reader
{range_reader(const T &low_bound, const T &upper_bound): low_(low_bound), high_(upper_bound){}T operator()(const std::string &s){T ret = default_reader<T>()(s);if (!(ret >= low_ && ret <= high_))throw cmdline::cmdline_error(s + " out of range" + constraint());return ret;}const std::string &constraint(){msg_.clear(); // ERROR 这里崩溃msg_ = "[" + detail::lexical_cast<std::string>(low_) + ", " + detail::lexical_cast<std::string>(high_) + "]";return msg_;}private:const T low_, high_;std::string msg_;
};
如上述代码,代码中存在调用 range_reader<T>::constraint()
方法的地方。有一处对该方法的调用必然在第一次访问 msg_
对象时引发崩溃,且根据崩溃信息确认错误是踩内存。
分析与解决
已经确认是踩内存,因此首先断点打到 msg_.clear()
这行,观察栈帧中的变量。发现无法读取 msg_
,且同生命周期的 low_
和 high_
的值都是未定义的,说明当前的 range_reader
对象没有被初始化。
于是顺着栈帧网上找,发现调用 range_reader<T>::constraint()
方法是在这里:
template<typename T, typename R>
class option_with_value_with_reader : public option_with_value<T>
{
public:option_with_value_with_reader(const std::string &full_name, char short_name, const class description &desc,bool required, const T default_value,R value_reader): option_with_value<T>(full_name, short_name, full_description(desc), required, default_value), reader_(value_reader){}//...
private:class description full_description(const class description &desc){// NOTE 这里不能调用reader_,因为reader_尚未被初始化(该函数在构造函数中被调用)return cmdline::description(desc.brief() + " " + reader_.constraint(), desc.detail());}//...R reader_;
};
option_with_value_with_reader
的构造函数中调用了 full_description()
,其中又调用了 range_reader<T>::constraint()
。而在 option_with_value_with_reader
的初始化列表中,reader_
的初始化放在了调用 full_description()
之后——即调用 full_description()
时 reader_
对象尚未初始化。
由于继承关系中的构造函数调用顺序始终是先调用基类的构造函数再调用子类的,所以这里无法通过调整初始化列表中的顺序来解决。最终的解决方案是调整代码逻辑,不要在构造函数的初始化列表中使用当前类的直接成员(即使是基类继承而来的成员),而是改为在构造函数体中调用 full_description()
函数。