防止在大型结构之间进行不必要的复制

wai*_*r92 2 c++ copy-elision c++17

我有巨大的结构 DataFrom 和 Data (实际上有不同的成员)。数据是从 DataFrom 创建的。

struct DataFrom{
    int a = 1;
    int b = 2;
};
static DataFrom dataFrom;   
struct Data{
    int a;
    int b;
};    
class DataHandler{
    public:
        static Data getData(const DataFrom& data2){
            Data data;
            setA(data, data2);
            setB(data, data2);
            return data;
        }
    private:
        static void setA(Data& dest, const DataFrom& source){
            dest.a = source.a;
        }
        static void setB(Data& dest, const DataFrom& source){
            dest.b = source.b;
        }
};
int main(){
    auto data = DataHandler2::getData(dataFrom); // copy of whole Data structure
    // ...
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

由于Data很大,在getData函数中,有整个Data结构的复制。可以以优雅的方式以某种方式防止这种情况吗?

我有一个想法:

static void getData( Data& data, const DataFrom& data2);
Run Code Online (Sandbox Code Playgroud)

但我更愿意将数据作为返回值而不是输出参数来检索。

ein*_*ica 7

这里有两个潜在的“复制危险”需要解决:

复制危害一:室外施工 getData()

在 的第一行 main(),您评论了“整个数据结构的副本” - 正如评论者所指出的,由于命名返回值优化(简称 NRVO),实际上不会复制该结构。几年前,您可以在这篇不错的博客文章中阅读到它:

Fluent{C++}:返回值优化

简而言之:编译器对其进行了安排,以便datagetData函数内部调用 from 时main(),实际上是datain main的别名。

复制危险 2:datadata2

第二个“复制恐慌”是用setA()setB()。在这里,您必须更加积极主动,因为您在同一个函数中确实有两个实时有效的结构体 -data并且data2getData(). 事实上,如果DataDataFrom只是大型结构 - 那么您将按照您编写代码的方式进行大量从data2to复制data

将语义移到救援中

但是,如果您DataFrom持有对某些已分配存储的引用,例如,std::vector<int> a而不是int[10000] a- 您可以从您的位置移动DataFrom而不是从它复制 - 通过getData()使用签名static Data getData(DataFrom&& data2)。在此处阅读有关搬家的更多信息:

什么是移动语义?

在我的例子,这将意味着你现在会用的原始缓冲区data2.a为您data-不复制该缓冲区其他地方的内容。但这将意味着您data2以后不能再使用,因为它的a领域已被蚕食、移出。

......或者只是“懒惰”。

除了基于移动的方法,您还可以尝试其他方法。假设你定义了这样的东西:

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? 
};    
Run Code Online (Sandbox Code Playgroud)

NowData不是一个简单的结构体;它更像是 a DataFrom(以及其他一些字段和方法)的外观。这有点不太方便,但好处是您现在创建的 aData仅包含对 a 的引用,DataFrom而没有复制任何其他内容。在访问时,您可能需要取消引用指针。


其他注意事项:

  • DataHandler被定义为一个类,但它看起来只是一个命名空间。您永远不会实例化“数据处理程序”。考虑阅读:

    为什么以及如何在 C++ 中使用命名空间?

  • 我的建议不涉及任何 C++17。移动语义是在 C++11 中引入的,如果你选择“懒惰”的方法 - 即使在 C++98 中也能工作。