C++性能优化篇:深拷贝、浅拷贝与移动构造

引言

最近在做性能优化,遇到了一个明显的性能问题,就是在大量数据做深拷贝的时候,程序耗时严重,几乎三分之一的时间都耗在了这里。于是乎仔细看了下此处的代码,发现这些深拷贝完全可以避免。

深拷贝、浅拷贝与移动构造

  • 浅拷贝 仅仅复制对象的数据成员的值,如果数据成员是指针,那么它会复制指针的值,而不是指针所指向的内容。这意味着两个对象会共享同一块内存区域。当其中一个对象修改了这块内存,另一个对象也会受到影响。如果没有特别定义拷贝构造函数或赋值操作符,编译器会自动生成默认的浅拷贝。
  • 深拷贝 深拷贝则不仅复制对象的数据成员,还会复制指针所指向的内容,即在堆上分配新的内存空间来存储被指针指向的数据。这样,即使两个对象有相同的指针成员,它们也会指向不同的内存区域,修改其中一个对象的指针所指向的数据不会影响另一个对象。深拷贝通常需要显式地实现拷贝构造函数和赋值操作符。
  • 移动构造 可以看出深拷贝和浅拷贝一般都发生在拷贝构造和赋值操作符中,虽然浅拷贝比深拷贝开销更小,但是其存在资源多次释放的问题。而移动构造可以避免这个问题,其实现方式通常是将源对象指针指向的资源转移到目标对象中,并将源对象的指针置为nullptr,以避免资源的重复释放。

代码优化

在了解了原理之后,就开始对我们的代码进行优化了。以下是优化前的示例代码:

class SampleData {
public:
  SampleData() {} // 默认构造函数
  // 带参构造函数
  SampleData(int size, int *data) {
    assert(data != nullptr);
    _size = size;
    _data = new int[_size];
    memcpy(_data, data, sizeof(int) * _size);
  }   
  SampleData(const SampleData& rhs) {
    _size = rhs._size;
    _data = new int[_size];
    memcpy(_data, rhs._data, sizeof(int) * _size);
  }
  SampleData& operator=(const SampleData& rhs) {
    _size = rhs._size;
    delete[] _data;
    _data = new int[_size];
    memcpy(_data, rhs._data, sizeof(int) * _size);
    return *this;
  }
  ~SampleData() {
    _size = 0;
    delete[] _data;
    _data = nullptr;
  }
  void processData() {
    // do something
  }

private:
  int _size{0};
  int* _data{nullptr};
};

void func() {
  std::vector<SampleData> data_arr;
  int n = 100000;
  constexpr int size = 1000;
  int input_data[size] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  for (int i = 0; i < n; ++i) {
    SampleData data(size, input_data);
    data_arr.push_back(data); // 产生了深拷贝
  }
}

int main()
{
  func();
  return 0;
}

优化之后,新增移动构造函数如下:

SampleData(SampleData&& rhs) {
	_size = rhs._size;
	_data = rhs._data;
	rhs._size = 0;
	rhs._data = nullptr;
}

func函数更改如下:

void func() {
  std::vector<SampleData> data_arr;
  int n = 100000;
  constexpr int size = 1000;
  int input_data[size] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
  for (int i = 0; i < n; ++i) {
    SampleData data(size, input_data);
    data_arr.push_back(std::move(data)); // 移动构造
  }
}

总结

  • 在做性能优化的时候,遇到数据量较大的场景,减少不必要的数据拷贝,是可以极大得提高程序性能的。
  • 移动语义可以帮助我们实现在浅拷贝的同时,又能避免资源重复释放的问题。但是使用移动语义也要注意,被移动的对象在移动完成之后,不能再被使用,否则可能出现未知错误。
  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
拷贝构造移动构造C++ 中用于对象复制和转移资源的特殊成员函数。它们的主要区别如下: 拷贝构造函数(Copy Constructor): - 用于创建一个新对象,该对象与另一个同类型对象完全相同。 - 在以下情况下调用:通过值传递参数、以值返回对象、通过赋值运算符进行赋值操作、在函数中按值传递对象等。 - 深度复制:拷贝构造函数通常会复制对象中的所有成员变量,并分配新的内存来存储数据,以确保新对象与原始对象完全独立。 移动构造函数(Move Constructor): - 用于将资源从一个对象转移到另一个对象,避免进行不必要的数据复制。 - 在以下情况下调用:通过右值引用传递参数、通过 std::move() 将对象强制转换为右值、通过返回右值引用从函数返回对象等。 - 浅复制:移动构造函数通常会直接将资源的指针从原始对象转移到新对象中,而不进行实际数据的复制。这可以提高性能,尤其是对于大型资源(如动态分配的内存)。 需要注意的是,如果一个类没有显式定义自己的拷贝构造函数或移动构造函数,编译器会自动生成默认的拷贝构造函数和移动构造函数。在某些情况下,编译器可能会对对象进行浅复制或深度复制,具体取决于对象的成员变量类型和复制/移动操作的方式。在某些情况下,也可以手动定义拷贝构造函数和移动构造函数来控制对象的复制和资源转移行为。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值