首页 > 解决方案 > How to safely copy structs with members pointing to other nested members in C++

问题描述

Consider these structs that come from an external library (I can't edit it)

struct Color;
struct Tires;
struct CarEF { // a car's external features
    Color* pColor;
    Tires* pTires;
};

Now, in my codebase, I want to use these structs and pass them to a factory-function that utilizes this struct to call a bunch of library constructors and create the Car (and dependent) objects. To consolidate all the info, I made this struct.

struct CarInfo { // captures all info needed to create 'Car'
    Color color{};
    Tires {};
    CarEF car_ef{};
    // other items
};

CarInfo create_car_info() {
    CarInfo info {
        .color = { /*  */ },
        .tires = { /*  */ },
        .car_ef = {
            .pColor = &info.color;
            .pTires = &info.tires;
        }
    };
    return info;
}

// factory function
Car carfactory(CarInfo) { 
    // create tire, create color ... etc (library code)
    // call Car-ctor with the above (library code)
}
CarInfo create_info = create_car_info(); // assume copy

The pointers in the CarInfo always point to other initialized members in the same struct instance.

Now, I'm afraid that the pointers might become invalid after the struct is copied. Sure, copy-elision could save me (will it?), but the question is, how do I safely copy this struct, so that the pointers in the create_info point to the correct items?

 info (local 
 stack variable 
 destroyed)              create_info (copied)
+----------------+     +----------------+
|                |     |                |
| +--->.color  <-+-----+-+    .color    |
| |              |     | |              |
| |              |     | |              |
| | +->.tyre  <--+-----+-+-+  .tyre     |
| | |            |     | | |            |
| | |            |     | | |            |
| | |  .car      |     | | |  .car      |
| +-+----.pColor |     | +-+----.pColor |
|   |            |     |   |            |
|   +----.pTyre  |     |   +----.pTyre  |
|                |     |                |
+----------------+     +----------------+

标签: c++c++20

解决方案


在您当前的代码中,create_car_info返回一个命名的局部变量。允许编译器,但不需要在返回CarInfo. 这称为命名返回值优化 (NRVO)

使用 C++17 和 20,如果您返回一个 pr 值,您将可以保证不会发生复制或移动,如下所示(关于此主题的 cpp 参考页):

CarInfo create_car_info() {
    // assuming there is a ctor which will set up car_ef to point to the right addresses.
    return CarInfo({/* color */}, {/* tires */});
}

您按值carfactory取值CarInfo,因此在这里制作了一个副本。您可以使用 const 引用来防止复制。

但是,从软件架构的角度来看,我建议不要依赖它。如果在任何时候,任何人决定只改变create_car_info一点点,它就会严重破裂。

似乎您在这里添加自定义复制构造函数的唯一动机是确保复制操作后指针仍然正确。从cppreference 上所述的零规则

具有自定义析构函数、复制/移动构造函数或复制/移动赋值运算符的类应专门处理所有权(遵循单一责任原则)。

所以,我建议你做的是找到一种方法,你不必担心你的指针,即使在传递你CarInfo的值之后也是如此。我可以想到两种方法来实现这一点:

  1. 通过指针(最有可能作为智能指针)传递CarInfo对象,以确保对象不会在内存中移动,并且其内部指针在其整个生命周期内保持有效。然后,将它传递给函数时不需要复制它。
  2. 只需将一个成员函数添加到您的CarInfo结构中,即可为其在内存中的当前位置提供正确的指针。即使对象是按值传递的,因此制作了一个副本,并且使用它的代码向其副本询问指针,它也会返回正确的指针。

这些方法中的任何一种对您有用吗?如果不是:为什么?


推荐阅读