自定义类中的移动与复制性能

Move vs copy performance in custom classes

本文关键字:复制 性能 移动 自定义      更新时间:2023-10-16

如果该类的成员主要是基本类型(如int等),那么与复制ctor相比,通过移动ctor创建类的实例如何(如果有的话)提高性能。 这些成员不是像复制CTOR中那样复制的吗?那么,在处理自定义类时,移动何时提供更好的性能呢?

如何(如果有的话)通过移动 ctor 创建类的实例 提高性能,与复制 CTOR 相比,如果成员说 类主要是基本类型,如 int 等,这些成员不就是 像在复制 CTOR 中一样复制?

在所有成员变量都是按值/POD 的情况下,根本不应该有任何区别。

因此,何时移动提供更好的性能,在处理 自定义类?

移动构造函数仅在新构造的对象可以从现有对象中"窃取"资源的情况下提供优势。

例如,假设您有一个临时std::string,其中包含小说"战争与和平"的全部内容 - 所有1440页。

在经典的复制构造函数情况下,如果要将该临时字符串分配给非临时std::string(例如成员变量或全局变量或其他任何内容),程序必须执行以下步骤:

  • 释放目标std::string可能持有的任何先前缓冲区
  • 分配一个长度为 (1440*chars_per_page) 字节的新缓冲区,供目标std::string保存
  • 将所有 1440 页数据从临时std::string的缓冲区复制到目标std::string
  • 缓冲区 删除临时字符串
  • 的缓冲区(当临时字符串超出范围时)

如您所见,这将效率低下,因为我们复制了大量数据,即使我们实际上从未需要数据的第二个副本。 但是,由于我们有一个为std::string实现的移动构造函数,因此C++11程序可以更智能,只需这样做:

  • 释放目标std::string可能持有的任何先前缓冲区
  • 将巨大的缓冲区从临时std::string传输到目标std::string(请注意,我们所做的只是将源字符串的缓冲区值的指针复制到目标字符串;特别是我们不需要复制,甚至不需要读取任何实际的 1440 页数据!
  • 将临时字符串指向缓冲区的指针值设置为 NULL(这样它就不会尝试释放它或稍后使用它)

。仅此而已;我们不必分配第二个大缓冲区,然后复制大量数据,只需滑动几个指针值即可实现所需的最终状态。 这是一个很大的性能胜利,我们可以侥幸逃脱,因为我们知道临时字符串无论如何都会被删除,所以"窃取"它持有的内部数据没有害处。

我不知道std::string是否算作"自定义类",但是只要您有一个动态分配内部状态的类,您就可以在自己的类中使用相同的技术。

与复制 ctor 相比,通过移动 ctor 创建类的实例如何(如果有的话)提高性能,如果该类的成员主要是基本类型,如 int 等。

其实不然。

这些成员不是像复制 ctor 中那样复制的吗?

是的。

那么,在处理自定义类时,移动何时提供更好的性能呢?

当你有"间接"资源时,比如指针引用的数据,这样你就可以"交换"指针,甚至避免接触它们指向的东西。(这就像把一辆大篷车从你的车上解开,然后重新挂在另一辆车上。就是这样。

如果类仅包含无论如何都必须复制的数据,则使类可移动没有任何好处。(这就像试图将大篷车的内容"移动"到另一辆大篷车上:你别无选择,只能实际进入,捡起所有东西并用手转移。

与复制 ctor 相比,通过移动 ctor 创建类的实例如何(如果有的话)提高性能

你误会了。通常,您不会选择对象是使用移动 ctor 还是复制 ctor 构造 - 它隐含在其定义的上下文中。所以没有太多的选择。当然,如果移动 ctor 不可用,则复制构造是后备。不过,不要把它当作一种选择。

如果所述类的成员主要是基本类型,如 int 等,这些成员不是像复制 CTOR 中那样复制吗?

它们可能根本不会被复制,即编译器可能完全省略对象的构造,只使用你传递的对象。但是,如果构造确实发生,那么是的,ints等将被复制(假设默认移动ctor)。

那么,在处理自定义类时,移动何时提供更好的性能呢?

再一次,你想错了。移动构造函数用于移动,复制构造函数用于复制。对于某些类,移动构造函数可能更快(例如,如果您可以切换分配的缓冲区等);对于某些人来说,事实并非如此。

延伸阅读:Andrzej的C++博客上对这两种CTOR的合理介绍。