4

新的 move-constructor/move-operator 允许我们转移对象的所有权,从而避免使用(昂贵的)复制构造函数调用。但是是否可以避免构造临时对象(不使用返回参数)?

示例:在下面的代码中,构造函数被调用了 4 次——但理想情况下,我想做的是避免在 cross 方法中构造任何对象。使用返回参数(例如void cross(const Vec3 &b, Vec3& out)是可能的,但很难读。我对更新现有变量感兴趣。

#include <iostream>

using namespace std;

class Vec3{
public:
    Vec3(){
        static int count = 0;
        id = count++;
        p = new float[3];
        cout << "Constructor call "<<id <<" "<<p<< " "<<this<< endl;
    }

    ~Vec3(){
        cout << "Deconstructor call "<<id << " "<<p<<" "<<this<< endl;
        delete[] p;
    }

    Vec3(Vec3&& other)
    : p(nullptr) {
        cout << "Move constructor call "<<id << " "<<p<<" "<<this<< endl;
        p = other.p;
        other.p = nullptr;
    }

    Vec3& operator=(Vec3&& other) {
        cout << "Move assignment operator call from "<<other.id<<" to "<<id << " "<<p<<" "<<this<< endl;
        if (this != &other) {
            p = other.p;
            other.p = nullptr;
        }
        return *this;
    }

    Vec3 cross(const Vec3 &b){
        float ax = p[0], ay = p[1], az = p[2],
            bx = b.p[0], by = b.p[1], bz = b.p[2];
        Vec3 res;
        res.p[0] = ay * bz - az * by;
        res.p[1] = az * bx - ax * bz;
        res.p[2] = ax * by - ay * bx;
        return res;
    }

    float *p;
    int id;
};


int main(int argc, const char * argv[])
{
    Vec3 a,b,c;
    a = b.cross(c);
    return 0;
}
4

4 回答 4

4

另一种解决方案是从 中返回一个“表达式对象” a.cross(b),推迟计算,直到将这样的对象分配给c,然后在operator=您实际进行计算:

 struct Vec3
 {

      CrossProduct cross(const Vec3& b);

      Vec3& operator=(CrossProduct cp)
      {
          do calculation here putting result in `*this`
      }
 }

并添加类似的构造机制等等。

这涉及更多,但许多 C++ 数学库都使用这种设计模式。

于 2013-06-26T11:54:04.150 回答
2

如果您直接分配新值:

Vec3 a = b.cross(c);

那么有可能RVO会生效,以后没有临时搭建和搬迁。确保您正在使用优化进行编译。返回的值将被就地构造成 a.

在堆上分配 3 个浮点数的数组似乎是性能杀手。使用类似 C 的数组float p[3]std::array<float, 3>应该执行得更好。

于 2013-06-26T11:25:48.067 回答
1

要更新现有变量,您可以使用 out 参数:

// out parameter version
void cross(const Vec3 &b, Vec3& res){
    float ax = p[0], ay = p[1], az = p[2],
        bx = b.p[0], by = b.p[1], bz = b.p[2];
    res.p[0] = ay * bz - az * by;
    res.p[1] = az * bx - ax * bz;
    res.p[2] = ax * by - ay * bx;
    return res;
}

当返回值版本用作初始值设定项时(但在分配给现有对象时不会),RVO 将省略构造函数:

// return value version (RVO)
Vec3 cross(const Vec3& b)
{
    Vec3 t; cross(b, t); return t;
}

您还可以提供结果对象的突变器:

// assignment version
void set_cross(const Vec3& a, const Vec3& b)
{
    a.cross(b,*this);
}

如图所示,所有三个成员函数都可以有效地共存和重用彼此的代码。

于 2013-06-26T11:31:21.320 回答
0

这不是对您问题的直接回答,我只能做出很少的贡献,因为目前的答案涵盖了重点,但我想引导您注意您选择的基于堆的设计的缺点。

3d-Vector 很少单独出现。

当然,这取决于您必须进行的移动次数和对向量执行的操作次数。

如果你只使用一些单一的向量并做很多复制/移动的东西,你可以坚持你的堆基设计。但是,如果您有多个向量(例如在向量或数组中)并且想要对它们进行操作,那么如果您担心性能,我建议您不要进行堆分配。

std::vector<Vec3> a(20);
class con_Vec3 { double x, y, z; };
std::vector<con_Vec3> b(20);

该向量a将维护一个连续的指针块,指向浮点数,而浮点值又将驻留在内存中的其他位置,这意味着您的值实际上分散在内存中。相比之下,向量b将包含一个连续的 60double秒块,都在一个地方。

对于这两种情况,移动这样的 a 的成本std::vector将是相等的(每一种都类似于交换),但是如果你复制它们,基于堆的解决方案会更慢,因为会发生 21 个分配和 20 个副本,而在非堆解决方案中有一个分配和 20 个副本覆盖整个向量。

于 2013-06-26T12:26:32.847 回答