我有巨大的结构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:data
和data2
第二个";复制恐慌";与CCD_ 9和CCD_。在这里,您必须更加主动,因为在同一函数中确实有两个有效的活动结构——getData()
中的data
和data2
。事实上,如果Data
和DataFrom
只是大型结构,那么您将按照编写代码的方式,大量地从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
具有= delete
d复制构造函数,并更改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;
}
这将在没有任何额外副本的情况下进行编译——您可以在编译器资源管理器上看到它