C++ 我应该使用手动alloc来允许移动语义吗?

C++ 我应该使用手动alloc来允许移动语义吗?,c++,c++11,C++,C++11,我很想知道什么时候开始考虑使用移动语义而不是复制数据,这取决于数据的大小和类的使用情况。例如,对于Matrix4类,我们有两个选项: struct Matrix4{ float* data; Matrix4(){ data = new float[16]; } Matrix4(Matrix4&& other){ *this = std::move(other); } Matrix4& operator=(Matri

我很想知道什么时候开始考虑使用移动语义而不是复制数据,这取决于数据的大小和类的使用情况。例如,对于Matrix4类,我们有两个选项:

struct Matrix4{
    float* data;

    Matrix4(){ data = new float[16]; }
    Matrix4(Matrix4&& other){
        *this = std::move(other);
    }
    Matrix4& operator=(Matrix4&& other)
    {
       ... removed for brevity ...
    }
    ~Matrix4(){ delete [] data; }

    ... other operators and class methods ...
};

struct Matrix4{
    float data[16]; // let the compiler do the magic

    Matrix4(){}
    Matrix4(const Matrix4& other){
        std::copy(other.data, other.data+16, data);
    }
    Matrix4& operator=(const Matrix4& other)
    {
       std::copy(other.data, other.data+16, data);
    }

    ... other operators and class methods ...
};

我相信“手动”分配和解除分配内存会有一些开销,如果在使用这个类时真的会碰到move构造,那么内存大小如此小的类的首选实现是什么?是否确实总是首选移动到副本之上?

我可能会使用已经实现移动语义的stdlib容器(例如std::vector或std::array),然后我只需要移动向量或数组

例如,可以使用std::array或std::vector>来表示矩阵类型

我不认为这对4x4矩阵有多大影响,但对10000x1000可能会有影响因此,是的,矩阵类型的移动构造函数绝对值得,特别是如果您计划使用大量临时矩阵(当您想要使用它们进行计算时,这似乎很可能)。它还允许高效地返回Matrix4对象(否则,您必须使用by ref调用来避免复制)


与此无关,但可能值得一提:如果您决定使用std::array,请将矩阵作为模板类(而不是将大小嵌入到类名中)。

在第一种情况下,分配和取消分配是昂贵的,因为您是从堆中动态分配内存,即使你的矩阵是在堆栈上构造的,移动也很便宜(只是复制一个指针)

在第二种情况下,分配和解除分配是便宜的,但移动是昂贵的——因为它们实际上是复制品

因此,如果您正在编写一个应用程序,并且您只关心该应用程序的性能,“哪一个更好?”问题的答案可能取决于您创建/销毁矩阵的数量与您复制/移动矩阵的数量,并且在任何情况下,进行您自己的测量以支持任何猜测

通过进行测量,您还将检查编译器是否在您预期的移动位置执行了大量的复制/移动省略-结果可能与您的预期相反

此外,缓存位置可能会产生影响:如果在堆上为矩阵的数据分配存储,则要逐元素处理在堆栈上创建的三个矩阵可能需要非常分散的内存访问模式,这可能会导致更多缓存未命中

另一方面,如果使用在堆栈上为其分配内存的阵列,则同一缓存线可能能够保存所有这些矩阵的数据,从而提高缓存命中率。更不用说,为了访问堆上的元素,首先需要读取
数据
指针的值,这意味着访问的内存区域与保存元素的内存区域不同

因此,这个故事的寓意是:自己测量

另一方面,如果您正在编写一个库,并且无法预测客户端将执行多少构造/破坏vs移动/复制,那么您可以提供两个这样的矩阵类,并将常见行为分解为基类-可能是基类模板

这将为客户端提供灵活性,并为您提供足够高的重用度—无需两次编写所有通用成员函数的实现

这样,客户机可以选择最适合其使用的应用程序的创建/移动配置文件的矩阵类


更新:

正如注释中指出的,基于数组的方法比动态分配方法的一个优点是后者通过原始指针、
new
delete
进行手动资源管理,这迫使您编写用户定义的析构函数、复制构造函数、移动构造函数、复制赋值操作符、,和移动赋值运算符

如果您使用的是
std::vector
,则可以避免所有这一切,它将为您执行内存管理任务,并将您从定义所有这些特殊成员函数的负担中解救出来


也就是说,仅仅建议使用
std::vector
而不是进行手动内存管理这一事实——就设计和编程实践而言,这是一个很好的建议——并不能回答这个问题,而我相信最初的答案可以回答这个问题。

与编程中的其他一切一样,特别是在性能方面,这是一个复杂的权衡

在这里,您有两种设计:将数据保存在类中(方法1)或在堆上分配数据并在类中保留指向它的指针(方法2)

据我所知,以下是您正在进行的权衡:

  • 构造/销毁速度:方法2在这里实现起来比较简单,因为它需要动态内存分配和释放。但是,您可以使用自定义内存分配器来帮助解决这种情况,特别是在数据大小是可预测和/或固定的情况下

  • 大小:在4x4矩阵示例中,方法2需要存储额外的指针,加上内存分配大小开销(通常可以是4到32字节)。这可能是一个因素,也可能不是一个因素,但肯定必须加以考虑,特别是在类实例较小的情况下

  • 移动速度:方法2具有非常快的移动操作,因为它只需要设置两个指针。在方法1中,您别无选择,只能复制数据。然而,虽然能够依靠快速移动可以使你的鳕鱼