我可以在C++中使用memcpy来复制没有指针或虚函数的类

Sma*_*acL 31 c++ performance shallow-copy

说我有一个类,如下所示;

class MyClass
{
public:
  MyClass();
  int a,b,c;
  double x,y,z;
};

#define  PageSize 1000000

MyClass Array1[PageSize],Array2[PageSize];
Run Code Online (Sandbox Code Playgroud)

如果我的类没有指针或虚拟方法,使用以下是否安全?

memcpy(Array1,Array2,PageSize*sizeof(MyClass));
Run Code Online (Sandbox Code Playgroud)

我问的原因是,我正在处理非常大的分页数据集合,如此处所述,性能至关重要,而memcpy与迭代分配相比具有显着的性能优势.我怀疑它应该没问题,因为'this'指针是一个隐含的参数而不是存储的任何东西,但是我还应该注意其他任何隐藏的恶意吗?

编辑:

根据sharptooths评论,数据不包括任何句柄或类似的参考信息.

根据Paul R的评论,我已经分析了代码,在这种情况下避免复制构造函数的速度提高了4.5倍.这里的部分原因是我的模板化数组类比给定的简单示例稍微复杂一些,并且在为不允许浅复制的类型分配内存时调用了一个放置'new'.这实际上意味着调用默认构造函数以及复制构造函数.

第二次编辑

或许值得指出的是,我完全接受以这种方式使用memcpy是不好的做法,在一般情况下应该避免使用.它正在使用的特定情况是高性能模板化数组类的一部分,它包含一个参数'AllowShallowCopying',它将调用memcpy而不是复制构造函数.这对于诸如删除数组开头附近的元素以及将数据分入和分出二级存储之类的操作具有很大的性能影响.更好的理论解决方案是将类转换为一个简单的结构,但是考虑到这需要对大型代码库进行大量重构,避免它不是我想做的事情.

Joh*_*ing 14

根据标准,如果程序员没有为类提供复制构造函数,编译器将合成一个构造函数,该构造函数展示默认的成员初始化.(12.8.8)但是,在12.8.1中,标准也说,

可以通过初始化(12.1,8.5)以两种方式复制类对象,包括函数参数传递(5.2.2)和函数值返回(6.6.3),以及赋值(5.17).从概念上讲,这两个操作是由复制构造函数(12.1)和复制赋值运算符(13.5.3)实现的.

这里的操作词是"概念上的",根据Lippman,编译器设计者给出了"out"来实际在"琐碎"(12.8.6)隐式定义的拷贝构造函数中进行成员初始化.

实际上,编译器必须为这些类合成复制构造函数,这些类表现出行为,就好像它们正在进行成员初始化一样.但如果该类表现出"按位复制语义"(Lippman,第43页),则编译器不必合成复制构造函数(这将导致函数调用,可能内联)并执行按位复制.这个说法显然是在ARM中备份的,但我还没看好.

使用编译器验证某些东西是否符合标准总是一个坏主意,但是编译代码并查看生成的程序集似乎验证编译器没有在合成拷贝构造函数中进行成员初始化,而是执行以下操作memcpy:

#include <cstdlib>

class MyClass
{
public:
    MyClass(){};
  int a,b,c;
  double x,y,z;
};

int main()
{
    MyClass c;
    MyClass d = c;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

生成的程序集MyClass d = c;是:

000000013F441048  lea         rdi,[d] 
000000013F44104D  lea         rsi,[c] 
000000013F441052  mov         ecx,28h 
000000013F441057  rep movs    byte ptr [rdi],byte ptr [rsi] 
Run Code Online (Sandbox Code Playgroud)

...这里28hsizeof(MyClass).

这是在调试模式下在MSVC9下编译的.

编辑:

这篇文章的长篇小论是:

1)只要执行按位复制将显示与成员复制相同的副作用,标准允许琐碎的隐式复制构造函数执行memcpy而不是成员复制.

2)有些编译器实际上是做memcpys而不是合成一个简单的复制构造函数,它执行成员复制.


Cra*_*rks 12

让我给你一个经验的答案:在我们的实时应用程序中,我们一直这样做,并且它工作得很好.对于Wintel和PowerPC的MSVC以及Linux和Mac的GCC,即使对于具有构造函数的类也是如此.

我不能引用C++标准的章节和经文,仅仅是实验证据.

  • 当人们接受能够准确告诉他们他们想要听到的答案时,我喜欢它. (22认同)
  • 如果您确切知道将使用哪个编译器以及您将运行哪个硬件,那么通用性就不那么重要了. (2认同)
  • `甚至对于具有构造函数的类也会破坏类的POD类型(除非它们是自c ++ 11以来的默认类)并且产生UB.这个anwser怎么会得到这么多的赞成,更不用说它被接受了? (2认同)

Joh*_*web 9

可以.但首先要问自己:

为什么不使用编译器提供的复制构造函数来执行成员复制?

您是否遇到需要优化的特定性能问题?

当前的实现包含所有POD类型:当有人更改它时会发生什么?


小智 9

你的类有一个构造函数,因此在C结构的意义上不是POD.因此,使用memcpy()复制它是不安全的.如果需要POD数据,请删除构造函数.如果你想要非POD数据,那么控制构造是必不可少的,不要使用memcpy() - 你不能同时拥有这两者.


utn*_*tim 8

[...]但是我还应该注意其他任何隐藏的恶意吗?

是的:您的代码会做出既不建议也不记录的某些假设(除非您专门记录它们).这是维护的噩梦.

此外,你的实现基本上是黑客攻击(如果它是必要的,这不是一件坏事),它可能依赖于(不确定)当前编译器如何实现的东西.

这意味着如果你从现在起一年(或五年)升级编译器/工具链(或者只更改当前编译器中的优化设置),没有人会记住这个hack(除非你努力保持它可见)并且你可能最终手上有不明确的行为,开发人员在未来几年诅咒"谁做过这件事".

并不是说决定是不合理的,而是维护人员意外(或将会)意外.

为了最小化(意外?),我会根据类的当前名称将类移动到命名空间内的结构中,结构中根本没有内部函数.然后你明确表示你正在查看一个内存块并将其视为内存块.

代替:

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

你应该有:

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

这样你:

  • 明确表示MyClass :: Data是一个POD结构,而不是一个类(二进制它们将是相同或非常接近 - 如果我没记错的话,它们是相同的)但是这样,程序员也可以看到这些代码.

  • 在两年内集中使用memcpy(如果你必须更改为std :: copy或其他东西),你可以在一个点上完成它.

  • 将memcpy的使用保持在POD结构的实现附近.

  • "你的手上可能会出现未定义的行为" - 由于非平凡的构造函数,他已经**手上有**UB.这真正意味着所说的UB可能会在以后发现.而且仅仅因为UB目前没有破坏东西,并不意味着它应该被使用. (2认同)

Kir*_*sky 5

您可以memcpy用于复制POD类型的数组.添加静态断言为boost::is_pod真是个好主意.您的课程现在不是POD类型.

算术类型,枚举类型,指针类型和指向成员类型的指针都是POD.

POD类型的cv限定版本本身是POD类型.

POD阵列本身就是POD.结构或联合,其所有非静态数据成员都是POD,如果它具有以下内容,则它本身就是POD:

  • 没有用户声明的构造函数.
  • 没有私有或受保护的非静态数据成员.
  • 没有基类.
  • 没有虚拟功能.
  • 没有引用类型的非静态数据成员.
  • 没有用户定义的复制赋值运算符.
  • 没有用户定义的析构函数.

  • 他的班级不是POD. (2认同)