There are two potential "copy hazards" to address here:
Copy hazard 1: The construction outside getData()
On the first line of main()
, where you commented "copy of whole Data structure" - as commenters noted, the structure won't actually be copied, due to the Named Return Value Optimization, or NRVO for short. You can read about it in this nice blog post from a few years back:
Fluent{C++}: Return value optimizations
In a nutshell: The compiler arranges it so that data
inside the getData
function, when it is called from main()
, is actually an alias of data
in main.
Copy hazard 2: data
and data2
The second "copy scare" is with setA()
and setB()
. Here you must be more pro-active, since you do have two live, valid structs in the same function - data
and data2
within getData()
. Indeed, if Data
and DataFrom
are simply large structs - then you will be doing a lot of copying from data2
to data
, the way you wrote your code.
Move semantics to the rescue
If, however, your DataFrom
holds a reference to some allocated storage, say, std::vector<int> a
instead of int[10000] a
- you could move from your DataFrom
instead of copying from it - by having getData()
with the signature static Data getData(DataFrom&& data2)
. Read more about moving here:
What is move semantics?
In my example, this would mean you would now use the raw buffer of data2.a
for your data
- without copying the contents of that buffer anywhere else. But that would mean you can no longer use data2
afterwards, since its a
field has been cannibalized, moved from.
... or just be "lazy".
Instead of a move-based approach, you might try something else. Suppose you defined something like this:
class Data {
protected:
DataFrom& source_;
public:
int& a() { return source_.a; }
int& b() { return source_.b; }
public:
Data(DataFrom& source) : source_(source) { }
Data(Data& other) : source_(other.source) { }
// copy constructor?
// assignment operators?
};
Now Data
is not a simple struct; it is more of a facade for a DataFrom
(and perhaps some other fields and methods). That's a bit less convenient, but the benefit is that you now create a Data
with merely a reference to a DataFrom
and no copying of anything else. On access, you may need to dereference a pointer.
Other notes:
Your DataHandler
is defined as a class, but it looks like it serves as just a namespace. You never instantiate "data handlers". Consider reading:
Why and how should I use namespaces in C++?
My suggestions do not involve any C++17. Move semantics were introduced in C++11, and if you choose the "lazy" approach - that would work even in C++98.