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