返回(大)对象时复制开销

Copy overhead when returning (big) objects?

本文关键字:复制 开销 对象 返回      更新时间:2023-10-16

考虑一个简单的Matrix4x4 Identity方法的以下两个实现。

1:这个以 Matrix4x4 引用为参数,其中数据直接写入。

static void CreateIdentity(Matrix4x4& outMatrix) {
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            outMatrix[i][j] = i == j ? 1 : 0;
        }
    }
}

2:这个返回一个矩阵4x4,不带任何输入。

static Matrix4x4 CreateIdentity() {
    Matrix4x4 outMatrix;
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            outMatrix[i][j] = i == j ? 1 : 0;
        }
    }
    return outMatrix;
}

现在,如果我想实际创建一个身份矩阵,我必须做

Matrix4x4 mat;
Matrix4x4::CreateIdentity(mat);

对于第一个变体和

Matrix4x4 mat = Matrix4x4::CreateIdentity();

第二。

第一个显然产生了一个优点,即没有完成一个不必要的副本,同时它不允许将其用作右值;

想象一下
Matrix4x4 mat = Matrix4x4::Identity()*Matrix4x4::Translation(5, 7, 6);

最后一个问题:有没有办法尽可能避免使用像Matrix4x4::CreateIdentity();这样的方法时不必要的副本,同时仍然允许像我上一个代码示例中那样将该方法用作右值?编译器是否自动优化了它?我很困惑如何有效地完成这个(看似(简单的任务。也许我应该实现这两个版本并使用任何合适的版本?

您通常不需要太担心这一点,因为复制省略(在本例中为 NRVO1(是标准的一部分。

更详细一点(危险地(,返回矩阵的版本很可能最终会将其分配给调用函数的堆栈,并且仅在被调用的函数中初始化它,而不调用任何复制构造函数。

因此,除非有什么东西禁止这一点(您可以通过运行它并检查是否调用复制构造函数来发现(,否则您通常不需要担心它

如果复制省略不能发生(或者只是由于某种原因不会发生,例如,如果编译器不想这样做,因为它不必这样做(,那么你仍然可以确保提供一个移动构造函数,然后使用它代替2。这里的好处是,当您的 return 语句涉及转换为实际返回的类型时,它甚至可以工作。

引用:

  1. 如果函数按值返回类类型,并且 return 语句的表达式是具有自动存储持续时间的非易失性对象的名称,该对象不是函数参数或 catch 子句参数,并且与函数的返回类型具有相同的类型(忽略顶级 cv-限定(,则省略复制/移动。构造该本地对象时,它直接在存储中构造,否则函数的返回值将被移动或复制到该存储中。这种复制省略的变体称为NRVO,即"命名返回值优化"。

  2. 如果表达式是左值表达式,并且满足或

    将满足复制省略的条件,除了表达式命名函数参数之外,则重载解析以选择要用于初始化返回值的构造函数将执行两次:首先,如果表达式是右值表达式(因此它可以选择移动构造函数或引用 const 的复制构造函数(, 如果没有合适的转换可用,则使用左值表达式第二次执行重载解析(因此它可以选择引用非常量(的复制构造函数(。

    即使函数返回类型与表达式类型不同,上述规则也适用(复制省略需要相同的类型(。