You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

含虚析构函数的自定义MPI类型为何引发段错误?

Boost.MPI发送/接收含虚析构函数的派生类vector触发段错误的原因与解决方法

问题描述

基于Boost.MPI类层次序列化示例修改代码后发现:只要给基类animal添加虚析构函数,接收std::vector<bird>(bird是animal的派生类)时就会触发段错误。类中无指针成员,所有成员均为静态定义,理论上默认析构函数完全适用。

编译命令:

mpic++ mpidatatype.cpp -o out -lboost_mpi -lboost_serialization

示例代码:

#include <boost/mpi.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>
#include <iostream>
using namespace boost::archive;
std::stringstream ss;

class animal
{
private:
    friend class boost::serialization::access;
    template <typename Archive>
    void serialize(Archive &ar, const unsigned int version) {
         ar & legs_;
         ar& arms_;
        }
protected:
    int legs_;
    int arms_;
public:
    animal() = default;
    animal(const int legs) : legs_{legs},arms_{2} {}
    int legs() const { return legs_; }
    virtual void printName() = 0;
    virtual ~animal()=default; 

};
class bird : public animal
{
public:
    bird() = default;
    bird(int legs, bool can_fly) :
    animal{legs}, can_fly_{can_fly} {}
    bool can_fly() const { return can_fly_; }
    void printName() {
        std::cout<<"My name is bird"<<std::endl; 
    };
    virtual ~bird()=default;
private:
    friend class boost::serialization::access;
    template <typename Archive>
    void serialize(Archive &ar, const unsigned int version)
    {
        ar & boost::serialization::base_object<animal>(*this);
        ar & can_fly_;
    }
    bool can_fly_;
};
namespace boost { namespace mpi {
    template <>
    struct is_mpi_datatype<bird> : mpl::true_ {};
}}

int main(int argc, char* argv[])
{
    boost::mpi::environment env;
    boost::mpi::communicator world;
    bool isBirdMPIType = boost::mpi::is_mpi_datatype<bird>::value;
    std::cout << "Is bird a valid MPI datatype? " << std::boolalpha << isBirdMPIType << std::endl;

    if (world.size() > 1)
    {
        if (world.rank() == 0)
        {
            std::vector<bird> dummy ={bird (1,0), bird(2,1)};
            world.send(1, 0, dummy);
            
        }
        if (world.rank() == 1)
        {
            std::vector<bird> dummy(2, bird(1,1)); 
            world.recv(0, 0, dummy);
        }
    }
}

原因分析

  • 核心问题在于你特化了boost::mpi::is_mpi_datatype<bird>为mpl::true_,但含虚函数(包括虚析构函数)的类会引入虚函数表指针(vptr),这会改变类的内存布局。而Boost.MPI的is_mpi_datatype要求类型必须是**可平凡复制(trivially copyable)**的类型——即内存布局固定,可直接按字节拷贝,无需序列化逻辑。
  • 当类存在虚析构函数时,对象内存中会多出一个vptr成员,这个指针的地址仅在当前进程有效(不同进程的虚表地址独立)。直接通过MPI字节拷贝传输后,接收端对象的vptr指向了发送端进程的虚表地址,后续调用析构函数(比如vector销毁元素时)会访问非法内存,触发段错误。
  • 即便类中没有显式指针成员,虚函数的存在已经让类不再满足MPI原生数据类型的要求。

解决方法

  • 移除boost::mpi::is_mpi_datatype<bird>的特化:让Boost.MPI自动使用Boost.Serialization来完成对象的序列化/反序列化,而非直接按字节拷贝。Boost.Serialization会正确处理虚函数相关的内存结构,不会传输无效的vptr,而是在接收端重新构造对象的虚表指针。
  • 确保序列化逻辑正确:基类和派生类的serialize函数需正确实现,派生类必须通过boost::serialization::base_object序列化基类部分。

修改后的关键代码段(删除特化部分):

// 移除以下代码块
/*
namespace boost { namespace mpi {
    template <>
    struct is_mpi_datatype<bird> : mpl::true_ {};
}}
*/

内容的提问来源于stack exchange,提问作者MA19

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 09:34:54