6

我知道这是一个非常古老的辩论,在世界各地已经讨论过很多次。但是我目前在决定在特定情况下应该在静态数组和动态数组之间使用哪种方法而不是另一种方法时遇到了麻烦。实际上,我不会使用 C++11,我会使用静态数组。但我现在很困惑,因为两者可能有同等的好处。

第一个解决方案:

template<size_t N>
class Foo
{
    private:
        int array[N];

    public:
        // Some functions
}

第二种解决方案:

template<size_t N>
class Foo
{
    private:
        int* array;

    public:
        // Some functions
}

我不能碰巧选择,因为两者各有优势:

  • 静态数组更快,我们根本不关心内存管理。
  • 只要未分配内存,动态数组就不会加权。在那之后,它们不像静态数组那样好用。但是从 C++11 开始,我们可以从移动语义中获得很大的好处,而我们不能将其用于静态数组。

我不认为有一个好的解决方案,但我想得到一些建议,或者只是想知道你对这一切的看法。

4

3 回答 3

5

我实际上不同意“这取决于”。永远不要使用选项 2。如果您想使用翻译时间常数,请始终使用选项 1 或 std::array。您列出的一个优点,即动态数组在分配之前没有任何重量,实际上是一个可怕的、巨大的缺点,需要重点指出。

永远不要拥有具有多个构造阶段的对象。永远不能。这应该是通过一些大纹身来记忆的规则。只是永远不要这样做。

当你的僵尸对象还不是很活跃,虽然也不是很死,管理它们的生命周期的复杂性会成倍增长。你必须检查每一种方法,无论它是完全活着的,还是只是假装活着。异常安全需要在您的析构函数中使用特殊情况。您现在添加了必须在 N 个不同位置检查的需求(#methods + dtor),而不是一个简单的构造和自动销毁。编译器不在乎你是否检查。而其他工程师不会有这个需求广播,所以他们可能会以不安全的方式调整你的代码,使用变量而不检查。现在所有这些方法都有多种行为,具体取决于对象的状态,因此对象的每个用户都需要知道会发生什么。 僵尸会毁了你的(编码)生活。

相反,如果您的程序中有两个不同的自然生命周期,请使用两个不同的对象。但这意味着你的程序中有两种不同的状态,所以你应该有一个状态机,一种状态只有一个对象,另一种状态有两者,由异步事件分隔。如果两点之间没有异步事件,如果它们都适合一个功能范围,那么分离是人为的,你应该做单相构造。

转换时间大小应转换为动态分配的唯一情况是当大小对于堆栈而言太大时。然后进入内存优化,并且应该始终使用内存和分析工具对其进行评估,以查看什么是最好的。选项 2 永远不会是最好的(它使用裸指针 - 所以我们再次失去了 RAII 和任何自动清理和管理,添加不变量并使代码更复杂并且容易被其他人破坏)。向量(如位掩码所建议的)将是适当的第一个想法,尽管您可能不喜欢及时的堆分配成本。其他选项可能是应用程序图像中的静态空间。但同样,只有在您确定您有内存限制并且从那里做什么应该由实际可衡量的需求来确定时,才应该考虑这些。

于 2012-04-30T15:52:39.523 回答
4

两者都不使用。几乎在任何情况下都最好使用std::vector。在其他情况下,这在很大程度上取决于std::vector不足的原因,因此无法普遍回答!

于 2012-04-29T22:59:45.343 回答
4

我目前在决定在特定情况下应该使用哪个而不是另一个时遇到问题。

您需要逐个考虑您的选项以确定给定上下文的最佳解决方案 - 也就是说,无法进行概括。如果一个容器适用于所有场景,那么另一个容器就会过时。

如前所述,std在编写自己的实现之前考虑使用实现。

更多细节:

固定长度

  • 注意你消耗了多少堆栈。
  • 如果将其视为动态大小的容器,可能会消耗更多内存。
  • 快速复制。

可变长度

  • 重新分配和调整大小可能代价高昂。
  • 可能会消耗比需要更多的内存。
  • 快速移动。

更好的选择还需要您了解元素类型的创建、复制、分配等的复杂性。

如果您确实使用std实现,请记住实现可能会有所不同。

最后,您可以为这些类型创建一个容器,抽象实现细节并根据大小和上下文动态选择适当的数据成员——抽象通用接口背后的细节。这有时对于禁用功能或使某些操作(例如昂贵的副本)更加明显也很有用。

简而言之,您需要对类型和用法有很多了解,并衡量程序的几个方面,以确定特定场景的最佳容器类型。

于 2012-04-30T00:33:21.783 回答