微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

c – 我应该使用手动分配来允许移动语义吗?

我有兴趣了解何时应该开始考虑使用移动语义来支持复制数据,具体取决于数据的大小和类的用法.例如,对于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,data);
    }

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

我相信有一些开销必须“手动”分配和释放内存,并且考虑到在使用此类时真正触及move构造的机会,内存大小如此之小的类的首选实现是什么?真的总是首选移动副本吗?

解决方法

在第一种情况下,分配和释放是昂贵的 – 因为你是动态地从堆中分配内存,即使你的矩阵是在堆栈上构建的 – 并且移动很便宜(只是复制指针).

在第二种情况下,分配和解除分配是便宜的,但移动是昂贵的 – 因为它们实际上是副本.

因此,如果您正在撰写应用程序而您只关心该应用程序的性能,那么问题的答案是“哪一个更好?”可能取决于您创建/销毁矩阵的程度与复制/移动矩阵的程度 – 无论如何,请根据您自己的测量结果来支持任何猜想.

通过测量,您还将检查您的编译器是否在您希望进行移动的位置执行大量复制/移动操作 – 结果可能与您的预期相反.

此外,缓存局部性可能会产生影响:如果为堆上的矩阵数据分配存储,则要在堆栈上创建的三个矩阵可能需要相当分散的内存访问模式 – 可能导致更多缓存未命中.

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

因此,故事的寓意是:做自己的测量.

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

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

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

更新:

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

如果你使用std :: vector,你可以避免所有这些,它会为你执行内存管理任务,并且可以免除定义所有特殊成员函数的负担.

这就是说,建议使用std :: vector而不是进行手动内存管理这一事实 – 尽管它在设计和编程实践方面是一个很好的建议 – 并没有回答这个问题,而我相信原来的答案确实如此. .

原文地址:https://www.jb51.cc/c/110700.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐