在遗留 C++ 类中实现移动语义

NJM*_*JMR 0 c++ move-semantics c++11

我正在开发一个遗留应用程序,其类的名称Point3D如下所示......

template <class T>
class Point3D final
{
    T values[3];
public:
    Point3D();
    Point3D(T x, T y, T z);
    explicit Point3D(const T value);
    Point3D(const Point3D& point);
    explicit Point3D(const Point2D<T>& point);
    ~Point3D();
};
Run Code Online (Sandbox Code Playgroud)

这个类在很多地方都被用作vector<Point3D<double>>. 向量的大小为 > 10^5。此外,遗留代码将此向量作为值传递并按值返回。这使得应用程序非常慢。同样在很多地方,我们都有类似的代码,如下所示......

for(auto i: n){ // This loop runs for 10^4 times
    Point3D<double> vpos;

    vpos[X_COORDINATE] = /*Some calculation*/;
    vpos[Y_COORDINATE] = /*Some calculation*/;
    vpos[Z_COORDINATE] = /*Some calculation*/;

    Positions.push_back(vpos);
}
Run Code Online (Sandbox Code Playgroud)

为了提高性能,我计划修改Point3D要使用的类,move semantics如下所示......

template <class T> 
class Point3D final {
    T* values;
public:
    Point3D() {
        values = new T[3];
    }
    Point3D(T x, T y, T z) {
        values = new T[3];
    }
    explicit Point3D(const T value) {
        values = new T[3];
        
    }
    Point3D(const Point3D& point) {
        values = new T[3];
    }
    ~Point3D() {
        delete[] values;
    }

    T  operator [] (qint64 coordinate) const { return values[coordinate]; }
    T& operator [] (qint64 coordinate) { return values[coordinate]; }

    Point3D& operator = (const Point3D& point) {
        ...
        return *this;
    }

    Point3D(Point3D&& other) noexcept : values(other.values)
    {
        other.values = nullptr;
    }

    Point3D& operator=(Point3D&& other) noexcept
    {
        using std::swap;
        swap(*this, other);
        return *this;
    }
};
Run Code Online (Sandbox Code Playgroud)

我是新手move semantics,请让我知道任何其他提高性能的方法。谢谢

use*_*522 5

此外,遗留代码将此向量作为值传递并按值返回。这使得应用程序非常慢。

一般来说,您建议的修改会使情况变得更糟。如果是像 之类的简单类型,则复制或移动原件的成本Point3D非常小。它只会复制三个值。如果是复杂类型,那么正如另一个答案所暗示的那样,删除声明的析构函数和复制构造函数将导致移动语义自动正确实现。(这也称为“零规则”,让所有特殊成员函数都隐式实现。)TdoubledoubleT

如果您的问题是该类型向量的许多副本,则该类型上的移动语义将根本无法帮助您。复制向量仍然需要复制所有元素。移动还不够。

您需要在这些向量的使用站点调整您的代码。例如,如果您按值传递这样的向量:

void f(vector<Point3D<double>>);

void g() {
    vector<Point3D<double>> v;
    // fill v
    f(v);
}
Run Code Online (Sandbox Code Playgroud)

那么这将产生向量的不必要的副本。这是不必要的,因为调用inv后不再需要 's 数据。因此可以将其移动而不是复制到函数中:fg

void g() {
    vector<Point3D<double>> v;
    // fill v
    f(std::move(v));
}
Run Code Online (Sandbox Code Playgroud)

这将移动向量,而不是Point3D<double>。移动向量是一个恒定时间操作,无论是否为元素类型实现移动语义。它不会复制或移动任何元素。

如果 然而v的状态是在g调用之后使用的,那么就不能直接使用移动​​语义,因为移动语义会导致状态丢失。在这种情况下,您需要重新考虑函数的设计,并考虑例如通过const引用传递。

(但请注意,如果返回向量,则不应使用。例如,应该使用。Move在仅按名称直接返回局部变量的返回语句上是隐式的,并且执行隐式调用将抑制复制省略,这在某些情况下将消除复制/完全移动。)std::movereturn std::move(v);return v;std::move