在C++中使用指向数组中对象的指针更好吗

Is it better to use pointers to objects in an array in C++?

本文关键字:对象 指针 更好 数组 C++      更新时间:2023-10-16

我正在涉猎C++,并试图用指针来理解它。创建对象数组时,创建对象类型的数组还是指针的数组更好?

例如:

Block grid[size];Block* grid[size];

从我读到的内容来看,我的印象是,当使用对象时,使用指针来节省内存几乎总是更好的?最终,我将创建一个由这些对象动态分配的2D数组,我想知道是否值得麻烦地尝试将它们作为指针存储在多维数组中。

注意:我被告知要研究2D向量,当涉及到动态创建和分配它们时,我和这些数组一样困难。

提前感谢!

我认为你在混合一些东西。经典的C数组是指向第一个元素的指针。你所想到的是在调用函数或类似函数时被(深度)复制的昂贵类。这些昂贵的函数参数(字符串、向量等)最好作为引用(或指针)传递。

"现代C++"为您提供了足够的实用程序,如果您不编写自己的容器,就不应该单独使用任何分配。你通常使用指针来指向上下文中没有的东西。我还建议不要再使用经典的C阵列。使用

array<Block, size> grid; // compile-time constant size

而不是

Block grid[size]; // compile-time constant size

array<>将尝试在堆栈上。如果size在编译时未知,则必须在堆上分配内存。STL调用这些堆数组vector而不是

vector<Block> grid(size); // runtime variable size
if (grid.size() > 0) 
   cout << grid[0]; // use grid like an array

堆数组的旧C风格方法是手动分配内存,并在不再需要内存后释放内存。这是非常容易出错的,应该避免,除非你知道你做什么。

如前所述,与普通的旧数组(即指针)的区别在于调用函数时。在你必须传递一个指针和潜在的数组的大小之前

void fancy_old_function(Block* grid, size_t size) // BAD practise
{
    // do things
}
// ...
{
    Block grid[size];
    fancy_old_function(grid, size); // error prone
    ...
    // maybe alot later
    fancy_old_function(grid, 13); // ohoh! 13 < size?
}

在C++和大对象中,您应该将它们作为引用(或使其成为指针)传递,否则您的向量将被深度复制。

void fancy_new_function(vector<Block>& grid) // GOOD
{
   // do fancy stuff an potentially change grid
}

现在的缺点是,我们对动态和静态阵列有不同的签名:

template <size_t N>
void fancy_new_function(array<Block, N>& grid) // GOOD
{
   // do fancy stuff an potentially change grid
}

对经典C数组的一个巨大改进是,我们始终隐式地知道数组的大小。

你可以这样称呼你的fancy_new_function

{ 
   array<Block,size> grid1;
   fancy_new_function(grid1); // size is implicitly deduced
                              // or use grid1.size();
   vector<Block> grid2(size);
   fancy_new_function(grid2); // developer can use grid2.size()
}

HTH

我会使用Block grid[size],因为你为什么要创建一个指针数组(我能想到的唯一原因是在编译时创建一个未知大小的数组)就内存而言,指针有点贵,因为它们的地址和内容都存储在堆栈和堆上。当你处理完它们时,你不能忘记释放它们,否则你会出现内存泄漏。


更好的选择是std::array<Block, size> grid,甚至是std::vector<Block> grid

std::vector类似于std::array,但它是动态的,没有固定的大小(例如,您可以在运行时更改它)。

假设这是在一个函数中,它不会"保存"内存。一种实现使用更多的堆栈内存。另一种使用较少的堆栈内存,但使用较多的堆。

这完全使用堆栈:

Block grid[size];

这使用堆栈来存储指向对象的指针。用于存储对象的内存是堆:

Block* grid[size];

然而,如果这些是全球声明,那么是的,这是:

Block grid[size];

可能会浪费内存,因为它是为应用程序的生存期分配的,并且无法释放/调整大小。

"更好"的答案取决于您如何使用数组。

如果Block很小(2-8字节),或者你需要使用网格的每个元素,你最好做

Block grid [size] ;

几乎在所有情况下。

假设sizeof(Block)>512,并且您没有使用网格和的所有元素

  const int size = 10 ;

使用可能会更好

 Block grid [size] ; 

这可能是最有意义的。另一方面,假设

const in size = 2000000 ;

然后

Block *grid [size] ;

更有意义。

更好是一个品味问题:-)

在简单的用例中,对象数组可能更容易使用,因为您可以一次创建和销毁整个数组。它看起来更像C++惯用语言。

我能想到的例外是:

  • 性能原因与创建对象的成本有关。在这里,您只为具有最大大小的指针创建一个数组,并在需要时创建对象
  • 派生类的多态性。IMHO这是使用指针数组的最佳用例。因为如果你试图将派生对象复制到它的基类中,你会得到对象切片:基类副本将丢失只存在于派生类中的所有属性。因此,在这种情况下,您可以创建一个指向基类的指针数组(它甚至可以是抽象的),并用具体的实现填充它。您可以使用基类中的虚拟方法来操作数组中的任何对象