过度移动构造函数

move constructor overkill

本文关键字:构造函数 移动      更新时间:2023-10-16

我有一个类,它包含一个指针,指向一大块分配的内存和许多基元类型成员。我正在考虑移动构造函数,并认为这是一个使用构造函数的绝佳机会。显然,指针应该移到idk上,如果这是基元的好主意的话。

以下是该类的一个人为示例:

class Foo {
private:
long m_bar = 1;
/* 20+ similar members */
};

为了使它们能够移动,必须对它们进行动态分配。

class Foo {
public:
Foo(Foo && rhs) : m_bar(rhs.m_bar) { rhs.m_bar = nullptr; }
~Foo() { delete m_bar; }
private:
long *m_bar = new long{1};
};

我的问题是,在堆上分配的开销会抵消移动语义带来的性能提升吗?

如果有什么不同的话,我相信像这样的堆分配每个成员最终会更慢。除了最初的堆分配(仅在构造时执行)之外,持有指向堆上许多小的、不连续的数据成员的指针与CPU缓存策略配合不好。

有些类移动得很好,因为它们分配了大量内存(例如std::string)。在您的情况下,移动每个指针的成本与移动较小的数据类型的成本一样高。我认为这样做更快的唯一方法是,将较小的数据成员封装在堆分配的类/结构中(可能持有一个unique_pointer),并通过单个指针的移动来移动所有这些成员。

也就是说,这很可能是一个过早优化的情况。您可能想让代码按原样运行,并确定为类实现更复杂的移动语义确实可以帮助代码的性能。

Move语义只有在移动对象比复制对象快的情况下才会更快。在您的示例中,情况并非如此。复制long应该与将指针复制到long的速度相同。通过动态分配每个成员来添加移动语义几乎肯定会减慢速度,而不是加快速度。

可能导致更快移动构造函数的是使用PIMPL习惯用法。您将动态分配一个包含所有成员的类,主类将只包含指向该类的指针。然后,您的move构造函数所要做的就是将指针复制到实现类。

如果基元既不比指针大,也不比指针(不知何故)复制成本高,那么动态分配它只是额外的成本。

您可能希望使原件无效,但这不需要指针。