在C++的vector里使用对象及指针

C++的函数使用值传递,以指针或对象作为参数时会有很大的差异,本文通过例子介绍这些差异。

以对象方式使用

下面的代码将创建一个vector对象,并通过for循环添加8个Button对象,

vector<Button> bs1;
for (auto i = 0; i < 8; i++) {
  Button button {i, i}; // 在栈上创建对象
  bs1.push_back(button); // 复制对象
  // 超出button的作用域,button被自动删除
}

由于使用值传递,在调用push_back方法时,Button的复制构造函数会被调用,所以添加到bs1里的实际是button被复制后的新对象,原来的button对象在本次循环结束时被自动删除。

同时由于vector内部使用类似数组的实现,添加元素时可能会调整数组大小,在调整时会复制原来所有的元素,所以在上面的循环里,生成的对象数量数量会远大于我们预期的数量。

除了默认复制对象外,我们也可以使用move函数强制移动对象,

  // 移动对象,会调用移动构造函数
  bs1.push_back(move(button));

或者将构造函数作为右值使用,避免循环时的复制,

  bs1.push_back(Button{i, i});

但后面两种方式仍然无法避免vector调整内部数据大小时的对象复制。

以指针方式使用

指针指向对象的地址。使用指针时需要确保指针指向的对象仍然有效。例如下面的使用方式就是错误的:

vector<Button *> bs0;
for (auto i = 0; i < 8; i++) {
  // 错误使用方式!
  Button button {i};
  bs0.push_back(&button);
}

在上面的代码里,添加到bs0的是栈对象的地址,但这些对象会在for的每个循环结束时被删除,结果是bs0里指针指向的都是垃圾数据。

正确的方式是通过new方式生成的对象,此时这些对象会被分配到堆内存上,付出的代价是我们必须在使用结束时自行删除这些对象,否则会出现内存泄漏问题。

vector<Button *> bs2;
for (auto i = 0; i < 8; i++) {
  Button *button = new Button(i);
  // 这里只需要复制指针
  bs2.push_back(button);
}
// 不使用bs2时,需要删除其中指针指向的对象
for (auto p: bs2){
  delete p;
}
bs2.clear();

关于C++的引用

C++的引用相当于对象的别名,

int a = 8;
int &b = a;
b = 9;
// a值为9

需要注意引用的声明与初始化方式,&出现在表达式左侧,与出现在右侧时(表示对象内存地址)表示不同含义。

源码

Gist