Reputation: 122
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 |
| | | |
+----------------+ +----------------+
Upvotes: 1
Views: 331
Reputation: 3422
In your current code, create_car_info
returns a named local variable. The compiler is allowed, but not required to omit the move- or copy-construction when returning the CarInfo
. This is called named return value optimization (NRVO)
With C++17 and 20, you would have a guarantee that no copy or move takes place if you were returning a pr-value, like this (cpp reference page on this topic):
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 */});
}
Your carfactory
takes a CarInfo
by value, so here, a copy is made. You could use a const reference to prevent the copy.
However, from a software architectural point, I would advise against relying on this. If at any point, anyone decides to change create_car_info
just a tiny bit, it will badly break.
It seems like your only motivation to add a custom copy-constructor here is to make sure the pointers are still correct after the copy operation. From the rule of zero as stated on cppreference:
Classes that have custom destructors, copy/move constructors or copy/move assignment operators should deal exclusively with ownership (which follows from the Single Responsibility Principle).
So, what I'd suggest you to do, instead, is to find a way you do not have to worry about your pointers, even after passing your CarInfo
by value. I can think of two ways to achieve that:
CarInfo
object by pointer, most likely as smart-pointer, to ensure that the object is not moved in memory and its internal pointers stay valid for its whole lifetime. Then, it wouldn't need to be copied when passing it to functions.CarInfo
struct that gives you the correct pointers for its current location in memory. Even if the object is passed by value, and thus a copy is made, and the code using it asks its copy for the pointers, it will get back correct pointers.Would any of these approaches work for you? If not: Why?
Upvotes: 1