【问题标题】:Static arrays VS. dynamic arrays in C++11静态数组 VS。 C++11 中的动态数组
【发布时间】:2012-05-09 17:49:57
【问题描述】:

我知道这是一个非常古老的辩论,已经在世界各地讨论过很多次。但是我目前在决定在特定情况下应该在静态数组和动态数组之间使用哪种方法而不是另一种方法时遇到了麻烦。实际上,我不会使用 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 开始,我们可以从移动语义中获得很大的好处,而静态数组则无法使用。

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

【问题讨论】:

  • 你的两个解决方案应该是:你的第一个,或者一个向量并且没有 size_t 模板。您应该避免使用可以毫无问题地使用 STL 容器的原始指针。
  • @fontanini:如果要针对 C++11 进行修改,那么两个解决方案应该是 std::array&lt;T,N&gt;std::vector&lt;T&gt;
  • @DavidRodríguez-dribeas 是的,先生!

标签: c++ arrays templates c++11 move-semantics


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 是的,您的评论很有道理。再次阅读我的代码时,我意识到我实际上从来没有僵尸对象,因为内存是在构造时分配的。所以不用担心。是的,唯一的优点是最终从移动操作中获得收益(如果使用,已经由 std::vector 提供)。与 std::array 相比,使用向量的唯一缺点是会有一些开销。但我不知道 std::array 是否有移动语义...
  • 在堆栈上,您通常不需要移动,您需要返回值优化,虽然特定于编译器,但相当普遍。可能使用移动的其他情况几乎总是可以就地完成(无副本),或者当需要副本时,实际上需要它(将一种类型的内存放入不同的位置),因此移动不起作用。
【解决方案2】:

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

【讨论】:

  • 我已经知道这个经验法则。我将用于专业工作,我会使用它。但是,我喜欢尝试并从尝试中获得经验。此外,在目前的情况下,除了随机访问之外,我真的不需要任何其他东西。这就是为什么我考虑尝试这样做的原因。而且,是的,由于 std::vector 依赖于实现,如果我使用这个类的许多实例,可能会有很大的开销,因为数组的大小并不是真的要在之后改变。
【解决方案3】:

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

您需要逐个考虑您的选择,以确定给定上下文的最佳解决方案——也就是说,不能一概而论。如果一个容器适用于所有场景,那么另一个容器就会过时。

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

更多细节:

固定长度

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

可变长度

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

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

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

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

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

【讨论】:

  • 在本例中,由于我需要 N
  • @ex0du5 示例:成员交换和移动对于向量来说很快。数组不需要分配/锁定、增长/锁定或不适当的大小调整。
  • @ex0du5 这是复制成本的一部分
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-09
  • 2018-07-29
  • 2013-07-20
  • 1970-01-01
  • 2012-11-15
  • 1970-01-01
  • 2021-09-25
相关资源
最近更新 更多