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

如何从shared_ptr正确拷贝数据至普通结构体?

问题背景

在两个模块间交换结构体数据:模块1提供结构体的std::shared_ptr,模块2需要普通结构体。尝试直接将shared_ptr传入memcpy时出现类型转换错误,目前采用解引用后取地址的方式(&(*one))实现拷贝,但担心存在误用。

真实场景为ROS2消息回调,接口形如:

void topic_callback(const pck::msg::msg1::SharedPtr msg) const

实际结构体嵌套且元素数量庞大,已通过断言校验源和目标结构体大小一致。现咨询:

  1. 有没有比memcpy更优的拷贝方法?
  2. 如何正确指定拷贝的源和目标?
  3. 效率(速度、内存占用)方面需要注意什么?

附示例代码:

#include <iostream>
#include <memory>
#include <cstring>

struct MyStruct1 {
 int x;
 double y;
 long long z;
};

struct MyStruct2 {
 int x;
 double y;
 long long z;
};

int main() {

  auto one = std::shared_ptr<MyStruct1>(new MyStruct1());

  MyStruct2 two;

  one->x = 10;
  one->y = 3.141;
  one->z = 1e9;

  std::cout << "Size of one: " << sizeof(one) << "\n";    // Size of one: 16
  std::cout << "Size of *one: " << sizeof(*one) << "\n";  // Size of *one: 24
  std::cout << "Size of two: " << sizeof(two) << "\n";    // Size of two: 24

  memcpy(&two, &(*one), sizeof(two));

  std::cout << "two.x: " << two.x << "\n";  // two.x: 10
  std::cout << "two.y: " << two.y << "\n";  // two.y: 3.141
  std::cout << "two.z: " << two.z << "\n";  // two.z: 1000000000

  return 0;
}
解答

1. 比memcpy更优的拷贝方法

首先明确前提:仅当两个结构体为标准布局(standard-layout)且内存布局完全一致时,memcpy才是合法且安全的。如果满足这个条件,memcpy本身已是性能极佳的选择——它是系统优化的内存拷贝函数,能利用SIMD等硬件指令实现批量复制。

若追求更安全或更易维护的方案,可选择:

  • 显式成员赋值:逐个拷贝结构体成员,完全规避内存布局依赖问题。但对于庞大的嵌套结构体,手动编写繁琐,可借助ROS2消息生成工具自动生成转换函数。
  • std::bit_cast(C++20+):标准库提供的类型安全内存重解释方法,编译期会校验源和目标类型的大小是否匹配,可读性优于memcpy:
    two = std::bit_cast<MyStruct2>(*one);
    
    注意:std::bit_cast要求源和目标类型均为可平凡复制(trivially copyable),这也是memcpy合法的必要条件。
  • 自定义转换函数:在模块间定义专属转换接口,比如给MyStruct1添加to_MyStruct2()成员函数,内部处理拷贝逻辑。这种方式最易维护,后续结构体修改时只需更新转换函数即可。

2. 正确指定拷贝的源和目标

你当前的&(*one)写法是正确的,但可以简化为one.get()——shared_ptr::get()直接返回底层对象的裸指针,语义更清晰:

memcpy(&two, one.get(), sizeof(two));

关键注意事项:

  • 确保源指针有效:拷贝前需确认shared_ptr非空,可添加断言:assert(one != nullptr);
  • 编译期校验大小匹配:运行期断言不如编译期检查及时,建议添加:
    static_assert(sizeof(MyStruct1) == sizeof(MyStruct2), "Struct sizes must match");
    
    避免因结构体修改导致大小不匹配的问题。
  • 目标内存有效可写:目标必须是已初始化的普通结构体(而非未分配内存的指针),&two需指向足够容纳结构体的有效内存区域。

3. 效率方面的注意点

速度优化

  • 优先选择std::bit_cast或memcpy:对于可平凡复制的结构体,这两种方法的拷贝速度接近硬件极限,远快于手动成员赋值(尤其是大结构体场景)。
  • 避免不必要的拷贝:若模块2可直接使用shared_ptr,应优先传递智能指针共享底层对象,完全消除拷贝开销。仅当模块2需要修改数据且不能影响原对象时,才必须执行拷贝。
  • 批量拷贝优化:若需处理多个结构体对象,尽量一次性批量拷贝,减少函数调用的额外开销。

内存占用

  • 规避栈溢出风险:如果结构体体积庞大(如几MB),不要在栈上声明(示例中的MyStruct2 two;),应在堆上分配:
    auto two = std::make_unique<MyStruct2>();
    memcpy(two.get(), one.get(), sizeof(MyStruct2));
    
  • 管理堆内存生命周期:若目标对象在堆上分配,需确保正确释放,避免内存泄漏。
  • 禁止对含动态成员的结构体使用memcpy:如果结构体包含std::string、动态数组等成员,memcpy会直接复制指针,导致双重释放或悬空指针,此时必须使用深拷贝(如显式成员赋值或自定义转换函数)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 10:01:46