从外部函数构造std::function触发std::bad_function_call问题排查
问题原因:全局变量初始化顺序依赖导致的空std::function调用
你的问题核心是不同编译单元全局变量的初始化顺序未定义,和std::function本身的限制无关,具体分析如下:
问题触发流程
- 你在
main.cpp中定义了全局变量putGet,它的初始化依赖extern声明的getLine(定义在externs.cpp)和putLine函数。 - C++标准规定:不同编译单元(.cpp文件)中的全局变量初始化顺序是未指定的。也就是说,
main.cpp里的putGet可能在externs.cpp里的getLine完成初始化之前就被创建了。 - 当
bind被调用初始化putGet时,getLine这个std::function还处于空状态(没有绑定任何可调用对象),而bind的lambda捕获了这个空的std::function。 - 后续在
main中调用putGet(State())时,lambda内部执行ma(state),也就是调用空的std::function,这会直接触发标准规定的std::bad_function_call异常。
为什么之前的写法能正常工作
- 把externs内容放到main.cpp中:此时
getLine和putGet属于同一个编译单元,全局变量初始化顺序是从上到下,getLine先完成初始化,putGet捕获的是有效的std::function。 - putGet改为main局部变量:局部变量的初始化发生在
main函数执行时,此时所有全局变量(包括getLine)都已经完成初始化,getLine是有效的。 - 直接展开bind定义:相当于延迟了对
getLine的实际调用到main执行阶段,此时getLine已经完成初始化。
解决方法
方法1:避免全局变量依赖(最简单)
把putGet改为main函数的局部变量,就像你已经验证过的那样:
int main() { const auto putGet = bind<std::string, Empty>(getLine, putLine); (void)putGet(State()); return 0; }
方法2:用函数内静态变量保证初始化顺序
把全局的getLine包装成函数,利用函数内静态变量的首次调用初始化特性,确保在需要时才完成初始化:
// common.h // 替换原来的extern声明 IOMonad<std::string>::T getLine() { static const auto impl = [] (State s) { (void)s; std::string str; std::cin >> str; return std::make_pair(str, State()); }; return impl; } // main.cpp const auto putGet = bind<std::string, Empty>(getLine(), putLine);
或者直接把putGet也包装成函数:
// main.cpp const auto& putGet() { static const auto val = bind<std::string, Empty>(getLine, putLine); return val; } int main() { (void)putGet()(State()); return 0; }
方法3:使用Nifty Counter Idiom
如果必须保留全局变量形式,可以用这个惯用法保证跨编译单元的全局变量初始化顺序,但实现相对复杂,一般推荐前两种方法。
内容的提问来源于stack exchange,提问作者Benji Dial
相关产品推荐
相关产品推荐

