我可以在 C++ 中使用 memcpy 来复制没有指针或虚函数的类吗
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3021333/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Can I use memcpy in C++ to copy classes that have no pointers or virtual functions
提问by SmacL
Say I have a class, something like the following;
假设我有一个类,如下所示;
class MyClass
{
public:
MyClass();
int a,b,c;
double x,y,z;
};
#define PageSize 1000000
MyClass Array1[PageSize],Array2[PageSize];
If my class has not pointers or virtual methods, is it safe to use the following?
如果我的类没有指针或虚拟方法,使用以下内容是否安全?
memcpy(Array1,Array2,PageSize*sizeof(MyClass));
The reason I ask, is that I'm dealing with very large collections of paged data, as decribed here, where performance is critical, and memcpy offers significant performance advantages over iterative assignment. I suspect it should be ok, as the 'this' pointer is an implicit parameter rather than anything stored, but are there any other hidden nasties I should be aware of?
我问的原因是我正在处理非常大的分页数据集合,正如这里描述的那样,其中性能至关重要,并且 memcpy 比迭代分配提供了显着的性能优势。我怀疑它应该没问题,因为“this”指针是一个隐式参数而不是存储的任何东西,但是还有其他我应该注意的隐藏问题吗?
Edit:
编辑:
As per sharptooths comments, the data does not include any handles or similar reference information.
根据尖牙评论,数据不包括任何手柄或类似的参考信息。
As per Paul R's comment, I've profiled the code, and avoiding the copy constructor is about 4.5 times faster in this case. Part of the reason here is that my templated array class is somewhat more complex than the simplistic example given, and calls a placement 'new' when allocating memory for types that don't allow shallow copying. This effectively means that the default constructor is called as well as the copy constructor.
根据 Paul R 的评论,我已经对代码进行了概要分析,在这种情况下,避免复制构造函数的速度大约快 4.5 倍。这里的部分原因是我的模板化数组类比给定的简单示例要复杂一些,并且在为不允许浅复制的类型分配内存时调用放置“新”。这实际上意味着调用默认构造函数以及复制构造函数。
Second edit
第二次编辑
It is perhaps worth pointing out that I fully accept that use of memcpy in this way is bad practice and should be avoided in general cases. The specific case in which it is being used is as part of a high performance templated array class, which includes a parameter 'AllowShallowCopying', which will invoke memcpy rather than a copy constructor. This has big performance implications for operations such as removing an element near the start of an array, and paging data in and out of secondary storage. The better theoretical solution would be to convert the class to a simple structure, but given this involves a lot of refactoring of a large code base, avoiding it is not something I'm keen to do.
也许值得指出的是,我完全接受以这种方式使用 memcpy 是不好的做法,在一般情况下应该避免。使用它的特定情况是作为高性能模板化数组类的一部分,其中包括一个参数“AllowShallowCopying”,它将调用 memcpy 而不是复制构造函数。这对诸如删除数组开头附近的元素以及将数据分页进出二级存储等操作具有很大的性能影响。更好的理论解决方案是将类转换为简单的结构,但鉴于这涉及对大型代码库的大量重构,避免它不是我热衷于做的事情。
采纳答案by John Dibling
According to the Standard, if no copy constructor is provided by the programmer for a class, the compiler will synthesize a constructor which exhibits default memberwise initialization. (12.8.8) However, in 12.8.1, the Standard also says,
根据标准,如果程序员没有为类提供复制构造函数,编译器将合成一个显示默认成员初始化的构造函数。(12.8.8) 但是,在 12.8.1 中,标准还说,
A class object can be copied in two ways, by initialization (12.1, 8.5), including for function argument passing (5.2.2) and for function value return (6.6.3), and by assignment (5.17). Conceptually, these two operations are implemented by a copy constructor (12.1) and copy assignment operator (13.5.3).
类对象可以通过两种方式复制,通过初始化(12.1,8.5),包括用于函数参数传递(5.2.2)和用于函数值返回(6.6.3),以及通过赋值(5.17)。从概念上讲,这两个操作由复制构造函数 (12.1) 和复制赋值运算符 (13.5.3) 实现。
The operative word here is "conceptually," which, according to Lippmangives compiler designers an 'out' to actually doing memberwise initialization in "trivial" (12.8.6) implicitly defined copy constructors.
这里的操作词是“概念上”,根据Lippman 的说法,它为编译器设计者提供了在“平凡”(12.8.6)隐式定义的复制构造函数中实际进行成员初始化的“出路”。
In practice, then, compilers have to synthesize copy constructors for these classes that exhibit behavior as if they were doing memberwise initialization. But if the class exhibits "Bitwise Copy Semantics" (Lippman, p. 43) then the compiler does not have to synthesize a copy constructor (which would result in a function call, possibly inlined) and do bitwise copy instead. This claim is apparently backed up in the ARM, but I haven't looked this up yet.
在实践中,编译器必须为这些类合成复制构造函数,这些类表现出的行为就好像它们在进行成员初始化一样。但是,如果该类表现出“按位复制语义”(Lippman,第 43 页),那么编译器就不必合成复制构造函数(这将导致函数调用,可能是内联的)并改为按位复制。这个说法显然在ARM 中得到了支持,但我还没有查到这个。
Using a compiler to validate that something is Standard-compliant is always a bad idea, but compiling your code and viewing the resulting assembly seems to verify that the compiler is not doing memberwise initialization in a synthesized copy constructor, but doing a memcpy
instead:
使用编译器来验证某些东西是否符合标准总是一个坏主意,但是编译您的代码并查看生成的程序集似乎可以验证编译器没有在合成的复制构造函数中进行成员初始化,而是执行以下操作memcpy
:
#include <cstdlib>
class MyClass
{
public:
MyClass(){};
int a,b,c;
double x,y,z;
};
int main()
{
MyClass c;
MyClass d = c;
return 0;
}
The assembly generated for MyClass d = c;
is:
生成的程序集为MyClass d = c;
:
000000013F441048 lea rdi,[d]
000000013F44104D lea rsi,[c]
000000013F441052 mov ecx,28h
000000013F441057 rep movs byte ptr [rdi],byte ptr [rsi]
...where 28h
is the sizeof(MyClass)
.
...这里28h
是sizeof(MyClass)
。
This was compiled under MSVC9 in Debug mode.
这是在调试模式下在 MSVC9 下编译的。
EDIT:
编辑:
The long and the short of this post is that:
这篇文章的长短是:
1) So long as doing a bitwise copy will exhibit the same side effects as memberwise copy would, the Standard allows trivial implicit copy constructors to do a memcpy
instead of memberwise copies.
1) 只要执行按位复制会表现出与按成员复制相同的副作用,标准就允许简单的隐式复制构造函数执行 amemcpy
而不是按成员复制。
2) Some compilers actually do memcpy
s instead of synthesizing a trivial copy constructor which does memberwise copies.
2) 一些编译器实际上是做memcpy
s 而不是合成一个简单的复制构造函数来进行成员复制。
回答by Crashworks
Let me give you an empirical answer: in our realtime app, we do this all the time, and it works just fine. This is the case in MSVC for Wintel and PowerPC and GCC for Linux and Mac, even for classes that have constructors.
让我给你一个经验性的答案:在我们的实时应用程序中,我们一直在这样做,而且效果很好。在用于 Wintel 和 PowerPC 的 MSVC 以及用于 Linux 和 Mac 的 GCC 中就是这种情况,即使对于具有构造函数的类也是如此。
I can't quote chapter and verse of the C++ standard for this, just experimental evidence.
我不能为此引用 C++ 标准的章节和经文,只是实验证据。
回答by Johnsyweb
You could. But first ask yourself:
你可以。但首先问问自己:
Why not just use the copy-constructor that is provided by your compiler to do a member-wise copy?
为什么不直接使用编译器提供的复制构造函数来进行成员复制?
Are you having specific performance problems for which you need to optimise?
您是否有需要优化的特定性能问题?
The current implementation contains all POD-types: what happens when somebody changes it?
当前的实现包含所有 POD 类型:当有人更改它时会发生什么?
回答by Johnsyweb
Your class has a constructor, and so is not POD in the sense that a C struct is. It is therefore not safe to copy it with memcpy(). If you want POD data, remove the constructor. If you want non-POD data, where controlled construction is essential, don't use memcpy() - you can't have both.
你的类有一个构造函数,所以不是 C 结构意义上的 POD。因此用 memcpy() 复制它是不安全的。如果需要 POD 数据,请删除构造函数。如果您想要非 POD 数据,其中受控构造是必不可少的,请不要使用 memcpy() - 您不能同时拥有。
回答by utnapistim
[...] but are there any other hidden nasties I should be aware of?
[...]但是还有其他我应该注意的隐藏的坏事吗?
Yes: your code makes certain assumptions that are neither suggested nor documented (unless you specifically document them). This is a nightmarefor maintenance.
是的:您的代码做出了既不建议也不记录的某些假设(除非您专门记录它们)。这是维护的噩梦。
Also, your implementation is basically hacking (if it's necessary it's not a bad thing) and it may depend (not sure on this) on how your current compiler implements things.
此外,您的实现基本上是黑客攻击(如果有必要,这不是一件坏事)并且它可能取决于(不确定)您当前的编译器如何实现事物。
This means that if you upgrade compiler / toolchain one year (or five) from now (or just change optimization settings in your current compiler) nobody will remember this hack (unless you make a big effort to keep it visible) and you may end up with undefined behavior on your hands, and developers cursing "whoever did this" a few years down the road.
这意味着,如果您从现在起一年(或五年)后升级编译器/工具链(或者只是更改当前编译器中的优化设置),没有人会记得这个 hack(除非您努力使其可见)并且您最终可能会手上有未定义的行为,开发人员在几年后诅咒“谁做了这件事”。
It's not that the decision is unsound, it's that it is (or will be) unexpected to maintainers.
并不是这个决定是不合理的,而是它对维护者来说是(或将会是)出乎意料的。
To minimize this (unexpectedness?) I would move the class into a structure within a namespace based on the current name of the class, with no internal functions in the structure at all. Then you are making it clear you're looking at a memory block and treating it as a memory block.
为了尽量减少这种情况(意外?),我会将类移动到基于类的当前名称的命名空间内的结构中,结构中根本没有内部函数。然后,您要明确表示您正在查看内存块并将其视为内存块。
Instead of:
代替:
class MyClass
{
public:
MyClass();
int a,b,c;
double x,y,z;
};
#define PageSize 1000000
MyClass Array1[PageSize],Array2[PageSize];
memcpy(Array1,Array2,PageSize*sizeof(MyClass));
You should have:
你应该有:
namespace MyClass // obviously not a class,
// name should be changed to something meaningfull
{
struct Data
{
int a,b,c;
double x,y,z;
};
static const size_t PageSize = 1000000; // use static const instead of #define
void Copy(Data* a1, Data* a2, const size_t count)
{
memcpy( a1, a2, count * sizeof(Data) );
}
// any other operations that you'd have declared within
// MyClass should be put here
}
MyClass::Data Array1[MyClass::PageSize],Array2[MyClass::PageSize];
MyClass::Copy( Array1, Array2, MyClass::PageSize );
This way you:
这样你:
make it clear that MyClass::Data is a POD structure, not a class (binary they will be the same or very close - the same if I remember correctly) but this way it is also visible to programmers reading the code.
centralize the usage of memcpy (if you have to change to a std::copy or something else) in two years you do it in a single point.
keep the usage of memcpy near the implementation of the POD structure.
明确 MyClass::Data 是一个 POD 结构,而不是一个类(二进制它们将相同或非常接近 - 如果我没记错的话,相同)但是这样它对于阅读代码的程序员也是可见的。
在两年内集中使用 memcpy(如果您必须更改为 std::copy 或其他内容),您可以一次性完成。
将 memcpy 的使用保持在 POD 结构的实现附近。
回答by Kirill V. Lyadvinsky
You could use memcpy
for copying array of POD types. And it will be a good idea to add static assert for boost::is_pod
is true. You class is not a POD type now.
您可以memcpy
用于复制 POD 类型的数组。添加静态断言 for boost::is_pod
is true将是一个好主意。你的班级现在不是 POD 类型。
Arithmetic types, enumeration types, pointer types, and pointer to member types are POD.
A cv-qualified version of a POD type is itself a POD type.
An array of POD is itself POD. A struct or union, all of whose non-static data members are POD, is itself POD if it has:
- No user-declared constructors.
- No private or protected non-static data members.
- No base classes.
- No virtual functions.
- No non-static data members of reference type.
- No user-defined copy assignment operator.
- No user-defined destructor.
算术类型、枚举类型、指针类型和指向成员类型的指针都是 POD。
POD 类型的 cv 限定版本本身就是一个 POD 类型。
一个 POD 数组本身就是 POD。一个结构体或联合体,其所有非静态数据成员都是 POD,如果它有:
- 没有用户声明的构造函数。
- 没有私有或受保护的非静态数据成员。
- 没有基类。
- 没有虚函数。
- 没有引用类型的非静态数据成员。
- 没有用户定义的复制赋值运算符。
- 没有用户定义的析构函数。
回答by Matthieu M.
I will notice that you admit there is an issue here. And you are aware of the potential drawbacks.
我会注意到你承认这里有问题。而且您知道潜在的缺点。
My question is one of maintenance. Do you feel confident that nobody will ever include a field in this class that would botch up your great optimization ? I don't, I'm an engineer not a prophet.
我的问题是维护之一。你是否相信没有人会在这个类中包含一个会破坏你的优化的字段?我不知道,我是工程师而不是先知。
So instead of trying to improve the copy operation.... why not try to avoid it altogether ?
所以与其尝试改进复制操作.... 为什么不尝试完全避免它呢?
Would it be possible to change the data structure used for storage to stop moving elements around... or at least not that much.
是否可以更改用于存储的数据结构以停止移动元素……或者至少不会那么多。
For example, do you know of blist
(the Python module). B+Tree can allow index access with performances quite similar to vectors (a bit slower, admittedly) for example, while minimizing the number of elements to shuffle around when inserting / removing.
例如,您是否知道blist
(Python 模块)。例如,B+Tree 可以允许索引访问,其性能与向量非常相似(当然,慢一点),同时最大限度地减少插入/删除时要混洗的元素数量。
Instead of going in the quick and dirty, perhaps should you focus on finding a better collection ?
与其快速而肮脏,也许您应该专注于寻找更好的收藏?
回答by zoli2k
Calling memcpy on non-POD classes is undefined behaviour. I suggest to follow Kirill's tip for assertion. Using memcpy can be faster, but if the copy operation is not performance critical in your code then just use bitwise copy.
在非 POD 类上调用 memcpy 是未定义的行为。我建议按照 Kirill 的提示进行断言。使用 memcpy 可以更快,但如果复制操作在您的代码中不是性能关键,那么只需使用按位复制。
回答by INS
When talking about the case you're referring to I suggest you declare struct's instead of class'es. It makes it a lot easier to read (and less debatable :) ) and the default access specifier is public.
在谈论您所指的情况时,我建议您声明struct而不是class。它使它更容易阅读(并且不那么有争议:))并且默认访问说明符是公开的。
Of course you can use memcpy in this case, but beware that adding other kinds of elements in the struct (like C++ classes) is not recommended (due to obvious reasons - you don't know how memcpy will influence them).
当然,在这种情况下您可以使用 memcpy,但请注意,不建议在结构中添加其他类型的元素(如 C++ 类)(由于显而易见的原因 - 您不知道 memcpy 将如何影响它们)。
回答by Christopher
It will work, because a (POD-) class is the same as a struct (not completely, default access ...), in C++. And you may copy a POD struct with memcpy.
它会起作用,因为在 C++ 中,(POD-) 类与结构相同(不完全,默认访问......)。您可以使用 memcpy 复制 POD 结构。
The definition of POD was no virtual functions, no constructor, deconstructor no virtual inheritance ... etc.
POD 的定义是没有虚函数、没有构造函数、没有虚继承的解构函数……等等。