代码之家  ›  专栏  ›  技术社区  ›  SmacL

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

  •  27
  • SmacL  · 技术社区  · 14 年前

    比如说我有一个班级,类似下面这样的;

    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));
    

    我问的原因是,我正在处理大量的页面数据集合,如文中所述 here 在性能至关重要的情况下,与迭代分配相比,memcpy具有显著的性能优势。我怀疑这应该是正常的,因为“this”指针是一个隐式参数,而不是存储的任何内容,但是是否还有其他我应该注意的隐藏的问题?

    编辑:

    根据Sharptouths的评论,数据不包括任何手柄或类似的参考信息。

    根据PaulR的评论,我已经对代码进行了分析,在这种情况下,避免复制构造函数的速度大约是4.5倍。部分原因是我的模板化数组类比给出的简单示例要复杂一些,并且在为不允许浅复制的类型分配内存时调用一个位置“new”。这实际上意味着调用默认构造函数和复制构造函数。

    二次编辑

    也许值得指出的是,我完全接受以这种方式使用memcpy是不好的做法,在一般情况下应该避免使用。使用它的特定情况是作为高性能模板化数组类的一部分,该类包括一个参数“allowsHallowCopying”,它将调用memcpy而不是复制构造函数。这对操作有很大的性能影响,比如在数组开始附近删除一个元素,以及在辅助存储器中对数据进行分页。更好的理论解决方案是将类转换为一个简单的结构,但是考虑到这需要对一个大型代码库进行大量的重构,避免它不是我想要做的事情。

    10 回复  |  直到 11 年前
        1
  •  12
  •   John Dibling    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”来在“divial”(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;
    }
    

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

    000000013F441048  lea         rdi,[d] 
    000000013F44104D  lea         rsi,[c] 
    000000013F441052  mov         ecx,28h 
    000000013F441057  rep movs    byte ptr [rdi],byte ptr [rsi] 
    

    …在哪里 28h sizeof(MyClass) .

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

    编辑:

    这篇文章的长与短分别是:

    1)只要执行逐位复制会显示出与memberwise copy相同的副作用,标准就允许普通的隐式复制构造函数执行 曼皮西 而不是memberwise副本。

    2)有些编译器实际上是这样做的 曼皮西 而不是合成一个执行memberwise复制的普通复制构造函数。

        2
  •  11
  •   Crashworks    14 年前

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

    我不能引用C++标准的章节,只是实验证据。

        3
  •  9
  •   anon    14 年前

    你的类有一个构造函数,所以在C结构的意义上,pod不是。因此,用memcpy()复制它是不安全的。如果需要POD数据,请删除构造函数。如果您想要非POD数据,而控制结构是必需的,那么不要使用memcpy()——这两者都不能。

        4
  •  9
  •   johnsyweb    14 年前

    能够 . 但首先问问你自己:

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

    您是否存在需要优化的特定性能问题?

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

        5
  •  8
  •   utnapistim    11 年前

    […]但是还有其他隐藏的污点吗? 我应该知道?

    是:您的代码会做出一些既没有建议也没有文档记录的假设(除非您专门记录了它们)。这是 恶梦 用于维护。

    另外,您的实现基本上是黑客攻击(如果有必要的话,这不是一件坏事),它可能取决于(不确定)当前编译器是如何实现的。

    这意味着,如果您在一年(或五年)之后升级编译器/工具链(或只更改当前编译器中的优化设置),没有人会记得这一黑客行为(除非您努力保持其可见),您可能会以未定义的行为结束,开发人员会在几年后诅咒“谁做了这件事”的ROA。d.

    这并不是说决策不可靠,而是维护人员无法预料到(或将要预料到)。

    最小化(未预期?)我将根据类的当前名称将类移动到命名空间中的一个结构中,该结构中根本没有内部函数。然后你要清楚地看到一个内存块,并把它当作一个内存块。

    而不是:

    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));
    

    你应该有:

    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 );
    

    这样你:

    • 明确地说,myClass::data是一个pod结构,而不是一个类(二进制的,它们将是相同的或非常接近的,如果我正确地记住的话,也是相同的),但是这样,程序员读取代码时也可以看到它。

    • 在两年内集中使用memcpy(如果必须更改为std::copy或其他东西),只需一点。

    • 将memcpy的使用保持在pod结构的实现附近。

        6
  •  5
  •   Kirill V. Lyadvinsky    14 年前

    你可以使用 memcpy 用于复制pod类型的数组。最好为添加静态断言 boost::is_pod 是真的。你的课程现在不是pod类型。

    算术类型、枚举类型、指针类型和指向成员类型的指针都是pod。

    pod类型的cv限定版本本身就是pod类型。

    一个吊舱阵列本身就是吊舱。 结构或联合(其所有非静态数据成员都是pod)本身就是pod,如果它具有:

    • 没有用户声明的构造函数。
    • 没有私有或受保护的非静态数据成员。
    • 没有基类。
    • 没有虚拟功能。
    • 没有引用类型的非静态数据成员。
    • 没有用户定义的复制分配运算符。
    • 没有用户定义的析构函数。
        7
  •  3
  •   Matthieu M.    14 年前

    我会注意到你承认这里有一个问题。你知道潜在的缺点。

    我的问题是维修。你有信心这个类中没有人会包含一个会破坏你伟大优化的字段吗?我不是,我是工程师而不是先知。

    所以,不要试图改进复制操作…为什么不试着完全避免呢?

    是否可以更改用于存储的数据结构以停止移动元素…或者至少没有那么多。

    例如,你知道 blist (python模块)。例如,B+树允许索引访问,其性能与向量非常相似(当然要慢一点),同时最小化插入/删除时要随机移动的元素的数量。

    也许你应该专注于寻找更好的收藏品,而不是快速而肮脏的作品?

        8
  •  1
  •   zoli2k    14 年前

    在非pod类上调用memcpy是未定义的行为。我建议按照基里尔的建议来断言。使用memcpy可以更快,但是如果复制操作在代码中不是性能关键的,那么只需使用按位复制。

        9
  •  1
  •   INS    14 年前

    当谈到你所指的案件时,我建议你声明 结构 而不是 'es。它使读取更容易(并且争议更小):并且默认的访问说明符是公共的。

    当然,在这种情况下,您可以使用MeMCPY,但注意不要在结构(如C++类)中添加其他类型的元素(由于明显的原因,您不知道MeMCPY会如何影响它们)。

        10
  •  0
  •   Christopher    14 年前

    它会起作用,因为在C++中,(POD)类与结构(不完全,默认访问…)相同。你可以用memcpy复制pod结构。

    POD的定义是没有虚拟函数,没有构造函数,解构器,没有虚拟继承…等。