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.