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



我有巨大的结构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;
}

由于Data是巨大的,在getData函数中,存在对整个Data结构的复制。能以优雅的方式防止这种情况吗?

我有一个想法:

static void getData( Data& data, const DataFrom& data2);

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

有两个潜在的"复制危险";地址:

复制危险1:getData()外的施工

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

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

简而言之:编译器对其进行了排列,使得getData函数中的data在从main()调用时,实际上是data的别名。

复制危害2:datadata2

第二个";复制恐慌";与CCD_ 9和CCD_。在这里,您必须更加主动,因为在同一函数中确实有两个有效的活动结构——getData()中的datadata2。事实上,如果DataDataFrom只是大型结构,那么您将按照编写代码的方式,大量地从data2复制到data

将语义转移到救援

但是,如果您的DataFrom包含对某些已分配存储的引用,例如std::vector<int> a而不是int[10000] a,则您可以通过使getData()具有签名static Data getData(DataFrom&& data2)DataFrom中移动,而不是从中复制。阅读更多关于搬迁的信息:

什么是移动语义?

在我的示例中,这意味着您现在可以为data使用data2.a的原始缓冲区,而无需将该缓冲区的内容复制到其他任何位置。但这意味着您以后不能再使用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? 
};    

现在Data不是一个简单的结构;它更像是CCD_ 29的门面(也许还有其他一些字段和方法(。这有点不方便,但好处是您现在只创建了一个Data,其中只引用了DataFrom,而不复制任何其他内容。在访问时,您可能需要取消引用指针。


其他注释:

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

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

  • 我的建议不涉及任何C++17。Move语义是在C++11中引入的;懒惰;方法-即使在C++98中也能工作。

由于您已经用c++17对此进行了标记,因此您可以用一种防止任何复制发生(或能够发生(的方式编写代码,并且如果它进行编译,您将知道静态将不会进行任何复制。

C++17在从函数返回时保证了拷贝省略,从而确保在某些情况下不会发生拷贝。您可以通过更改代码以使Data具有= deleted复制构造函数,并更改getData以返回构造的对象来确保这一点。如果代码编译正确,您将确保没有发生复制(因为复制会触发编译错误(

#include <iostream>
struct DataFrom{
int a = 1;
int b = 2;
};
static DataFrom dataFrom;   
struct Data{
Data() = default;
Data(const Data&) = delete; // No copy
int a;
int b;
};    
class DataHandler{
public:
static Data getData(const DataFrom& data2){
// construct it during return
return Data{data2.a, data2.b};
}
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 = DataHandler::getData(dataFrom); // copy of whole Data structure
return 0;
}

这将在没有任何额外副本的情况下进行编译——您可以在编译器资源管理器上看到它