C++构造函数类外定义链接失败内联正常问题排查
跨线程共享mutex封装Messenger类的链接异常问题
问题复现
需求为在线程间共享std::mutex实现队列的跨线程安全访问,基于此编写了用于发送消息的Messenger类。
最初在头文件中声明的类结构如下:
class Messenger Messenger { public: Messenger(std::mutex& m); Messenger(); std::mutex& mutex; private: //variables zmq::context_t zmq_context; zmq::socket_t zmq_socket; };
类的成员函数实现在单独的Messenger.cpp源文件中编写,对应代码如下:
/** * Blank constructor */ Messenger::Messenger(){ } Messenger::Messenger(std::mutex& m) : zmq_context(1), zmq_socket(zmq_context, ZMQ_PUB), mutex(m) { }
主程序中按如下方式传入互斥锁、实例化Messenger对象:
//start code std::mutex mutex; auto msgr = std::make_unique<Messenger>(std::ref(mutex));
上述代码可正常通过编译阶段,但链接阶段会抛出错误,提示Messenger::Messenger(std::mutex&)存在未定义引用,完整链接错误日志如下:
/usr/bin/ld: CMakeFiles/ImuStream.dir/imu_stream.cpp.o: in function `std::_MakeUniq<Messenger>::__single_object std::make_unique<Messenger, std::reference_wrapper<std::mutex> >(std::reference_wrapper<std::mutex>&&)': imu_stream.cpp:(.text._ZSt11make_uniqueI9MessengerJSt17reference_wrapperISt5mutexEEENSt9_MakeUniqIT_E15__single_objectEDpOT0_[_ZSt11make_uniqueI9MessengerJSt17reference_wrapperISt5mutexEEENSt9_MakeUniqIT_E15__single_objectEDpOT0_]+0x41): undefined reference to `Messenger::Messenger(std::mutex&)' collect2: error: ld returned 1 exit status
如果将带std::mutex参数的构造函数改为类内内联定义,修改后的类结构如下,代码即可正常完成链接:
class Messenger { public: //Messenger(std::mutex& m); Messenger(std::mutex& m) : mutex(m) {}; Messenger(); std::mutex& mutex; private: //variables zmq::context_t zmq_context; zmq::socket_t zmq_socket; };
待解释的两个核心疑问:
- 为什么外联定义构造函数时会触发链接错误,改为类内内联定义后就可正常链接?
- 为什么内联定义构造函数时,初始化列表中没有初始化
zmq_context和zmq_socket成员,链接器也没有报错?
编译环境说明
项目使用CMake完成编译,使用的CMakeLists.txt文件内容如下:
cmake_minimum_required(VERSION 3.10) # set the project name project(ImuStream VERSION 1.0) #configure header for setting the version configure_file(ImuStreamConfig.h.in ImuStreamConfig.h) # add the executable add_executable(ImuStream imu_stream.cpp) #find cppzmq wrapper, installed by make of cppzmq find_package(cppzmq) target_link_libraries(ImuStream cppzmq) target_include_directories(ImuStream PUBLIC "${PROJECT_BINARY_DIR}" )
构建流程为:新建build目录,在目录内执行cmake ..命令后执行make命令。最终排查确认直接诱因是未将Messenger.cpp文件添加到CMake的编译配置中。
原因解析
外联定义报错、内联正常的核心逻辑
C++采用分离编译模式,以单个.cpp源文件(翻译单元)为单位独立完成编译,最后再由链接器统一拼接所有编译生成的目标文件、解析跨翻译单元的符号引用:
- 编译
imu_stream.cpp时,编译器只要能从引入的头文件里看到Messenger::Messenger(std::mutex&)的声明,就不会报语法错误,只会将该构造函数标记为待解析的外部符号,留到链接阶段查找对应实现。 - 由于CMake配置中
add_executable仅纳入了imu_stream.cpp,存放构造函数实现的Messenger.cpp根本没有被编译为目标文件,链接阶段找不到该构造函数对应的实现符号,自然会抛出未定义引用错误。 - 类内部直接定义的成员函数会被隐式标记为
inline属性,编译器会在每个调用该函数的翻译单元内直接生成对应的实现代码,不需要依赖其他目标文件提供的符号,因此即使Messenger.cpp没参与编译,链接阶段也能找到对应的构造函数实现,不会报错。
内联构造函数未初始化ZMQ成员不报错的原因
这里要区分编译器和链接器的职责边界:
- 链接器的核心职责是解析符号引用、拼接目标文件,根本不会检查类成员的初始化逻辑,只要所有被引用的函数、变量符号能找到对应实现,链接器就不会抛出错误。
- 类成员的初始化检查是编译阶段的工作。对于没有在构造函数初始化列表中手动初始化的非引用、非const成员,编译器会自动调用其默认构造函数完成初始化。上述内联构造函数中,
zmq_context和zmq_socket都支持默认构造,因此不存在语法错误,编译阶段也不会触发报错。
注意:该内联版本的构造函数没有按原有逻辑初始化ZMQ相关成员,运行时会因为context和socket未正确配置出现异常,实际使用时需要把对应初始化逻辑补到内联构造的初始化列表中。
内容的提问来源于stack exchange,提问作者confused
相关产品推荐
相关产品推荐

