考虑以下两种简单的Matrix4x4标识方法的实现。
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:这个没有任何输入就返回一个Matrix4x4。
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;
}
现在,如果我想真正创建一个Identity-Matrix,我必须这样做
Matrix4x4 mat;
Matrix4x4::CreateIdentity(mat);
为第一个变体和
Matrix4x4 mat = Matrix4x4::CreateIdentity();
第二个。
第一个显然产生的优势是不会完成单个不必要的复制,而不允许将其用作右值;想象
Matrix4x4 mat = Matrix4x4::Identity()*Matrix4x4::Translation(5, 7, 6);
最后一个问题:在使用像Matrix4x4::CreateIdentity();
这样的方法时,有没有办法避免不必要的副本,同时仍允许将该方法用作我上一个代码示例中的右值?甚至是编译器自动优化了吗?我很困惑如何有效地解决这个(看似)简单的任务。也许我应该实现这两个版本并使用适当的东西?
答案 0 :(得分:10)
鉴于copy elision(在这种情况下,NRVO 1 )是标准的一部分,你大多不需要太担心。
更详细一点(危险地),返回矩阵的版本很可能会最终将它分配到调用函数的堆栈上并且仅初始化它在被调用的函数中,没有调用任何复制构造函数。
因此,除非有什么东西阻止了这个(你可以通过运行它并检查复制构造函数是否被调用来找到),那么你主要不需要担心它
如果复制省略不能发生(或者因某种原因不会,例如编译器不想这样做,因为它不会拥有 > to),然后你仍然可以确保提供一个移动构造函数,然后用它来代替 2 。这里的好处是,当你的return语句涉及到实际返回类型的转换时它甚至会起作用。
<强>参考文献:强>
如果函数按值返回类类型,并且return语句的表达式是具有自动存储持续时间的非易失性对象的名称,该对象不是函数参数或catch子句参数,并且具有相同类型(忽略顶级cv资格)作为函数的返回类型,则省略复制/移动。构造该本地对象时,它直接在存储器中构造,否则将移动或复制函数的返回值。复制省略的这种变体被称为NRVO,“命名返回值优化”。
如果表达式是左值表达式并且满足复制精度的条件,或者将满足条件,除了表达式命名函数参数,那么重载决策以选择用于初始化返回值的构造函数执行两次:首先,好像表达式是一个rvalue表达式(因此它可以选择移动构造函数或引用const的复制构造函数),如果没有合适的转换可用,则第二次执行重载解析,使用左值表达式(因此可以选择复制构造函数引用非const)。
即使函数返回类型与表达式的类型不同,上述规则也适用(复制省略需要相同的类型)。