【问题标题】:Difference between vector of pointer and vector of values指针向量和值向量之间的差异
【发布时间】:2020-03-05 09:00:59
【问题描述】:

我听说 C++ 中不推荐使用指针,但我不明白为什么。

我的问题是我想创建一个类对象向量来管理我的类。

vector<MyClass> vectorOfClass;

当然,为了更好的性能,我应该使用类对象指针的向量?

vector<MyClass *> vectorOfClass;

或者是否可以创建类对象的引用向量?

vector<MyClass &> vectorOfClass;

所以我的问题是:

  • 这些方式有什么区别?
  • 在创建类对象向量方面最优化的是什么?

【问题讨论】:

  • I heard pointers are not recommended in C++ 到底在哪里?
  • @MutableSideEffect 通常最好避免使用指针,如果它们对于当前用例来说是不必要的。许多 java 或 C# 开发人员将使用 C++ 并开始在任何地方使用指针,只是为了能够在类型上使用 new
  • std::vector&lt;MyClass&gt; 应该是默认选择。
  • “当然,为了获得更好的性能,我应该使用类对象指针的向量?”自然?指针有它们的位置,但 C++ 与 C 不同,它提供了像 vector 这样很少受益的东西。在内部,它们使用指针来定位存储在堆上的对象,并使用 C 中的指针手动执行您必须执行的操作。
  • 当然,为了获得更好的性能,我应该使用类对象指针的向量? -- std::vector&lt;MyClass&gt; 保证所有这些 MyClass 实例都是在连续内存中(即更有可能在缓存中)。 std::vector&lt;MyClass*&gt; 并非如此。

标签: c++ class pointers memory vector


【解决方案1】:

考虑所有权并让其指导您的决定:


如果vector 拥有它们,那么最好使用

vector<MyClass> vectorOfObjects;

vector<std::unique_ptr<MyClass>> vectorOfObjects;

如果您必须有一个指针(指向多态基),但您仍希望 vector 拥有该对象,则最后一个非常有用。


如果所有权位于别处,但您希望不时更改每个指针,则使用

vector<MyClass*> vectorOfObjects;

但在这种情况下,您可能希望考虑使用智能指针:

vector<shared_ptr<MyClass>> vectorOfObjects;

最后,你不能使用vector&lt;MyClass &amp;&gt; vectorOfObjects;,但你可以使用std::reference_wrapper

vector<std::reference_wrapper<MyClass>> vectorOfObjects;

同样,这可能仅在 vector 不拥有对象时才有用。


至于性能,每次分配内存都可能有成本,因此在创建对象后使用指针会有所帮助,但最好从简单开始,然后进行衡量。 vector&lt;MyClass&gt; vectorOfObjects; 甚至可能表现更好。在你测量之前你不会知道。这已在this answer 中进行了探索。

【讨论】:

    【解决方案2】:

    当然,为了更好的性能,

    这不是假设那么简单。拥有一个指针向量意味着实际值只能通过 2 个指针重定向来达到 - 一个用于索引,下一个用于实际值访问。这些指针可以在内存中的任何位置。因此访问它们可能会导致大量缓存未命中。将对象保持在向量中是个好主意。仅当有足够的证据(= 分析结果)时才更改该模式。

    【讨论】:

      【解决方案3】:

      听说 C++ 中不推荐使用指针

      你可能误会了。虽然在某些情况下有更好的指针替代方案,以及可以(但不应该)不必要地使用指针的情况,但无论用例如何,都没有针对指针的一般建议。

      当然,为了更好的性能,我应该使用类对象指针的向量?

      指针并不是仅仅通过存在就可以提高性能的魔法。事实上,它们很可能会使性能恶化。通过指针间接不是免费的。

      或者是否可以创建类对象的引用向量?

      这是不可能的。向量的元素类型或任何其他容器不能被引用。可以改用std::reference_wrapper

      这些方式有什么区别?

      vector&lt;MyClass&gt;MyClass 对象存储在向量中。 vector&lt;MyClass*&gt; 将指针存储在向量中。 vector&lt;MyClass&amp;&gt;违反了vector的要求,格式不正确。

      什么是创建类对象向量的最佳优化?

      最有效的做法是什么都不做。根本不创建向量将至少与创建向量一样快,并且可能更快。在了解如何以最佳方式创建矢量之前,您必须首先了解通过创建矢量要达到的目的。

      【讨论】:

        【解决方案4】:

        或者是否可以创建类对象的引用向量?

        没有。引用不是 C++ 中的对象。因此,您不能创建引用数组或指向引用的指针。但是,您可以使用 std::reference_wrapper,它将引用包装在对象中。

        创建类对象向量的最佳优化是什么?

        始终视情况而定。根据您的数据进行衡量、分析并做出决定。

        这些方式有什么区别?

        它们以不同的方式存储。

        值向量在内存中如下所示:

        +----------------------+
        | std::vector<MyClass> |----
        +----------------------+   |
                                   |
           -------------------------
           |
           v
        +-------------+-------------+-------------+-------------+
        |   MyClass   |   MyClass   |   MyClass   |   MyClass   |
        +-------------+-------------+-------------+-------------+
        

        而指针向量看起来像这样:

        +-----------------------+
        | std::vector<MyClass*> |---
        +-----------------------+  |
                                   |
                  ------------------
                  |
                  v
               +-------+-------+-------+-------+
               |  ptr  |  ptr  |  ptr  |  ptr  |
               +-------+-------+-------+-------+
                   |       |      |          |
                   v       |      v          |
        +-------------+    | +-------------+ |
        |   MyClass   |    | |   MyClass   | |
        +-------------+    | +-------------+ |
                           v                 v
                +-------------+         +-------------+
                |   MyClass   |         |   MyClass   |
                +-------------+         +-------------+
        

        两者各有优缺点。

        对于值:

        • 专业版:在内存中是连续的。没有指针追逐,通常迭代速度非常快。
        • 专业版: 自动内存管理。 Vector 将管理它分配的每个值的内存。
        • 缺点:引用失效。调整向量的大小将使对其中对象的每个引用都无效。
        • 缺点: 使用重要对象调整大小的速度较慢。调整大小涉及移动对象。对于大型或非平凡对象,这可能会更慢。

        对于指针:

        • 专业版:没有引用失效。对象的地址由您管理。
        • 优点:更快的重新分配,因为这将是复制指针而不是移动对象的成本。
        • 缺点:迭代缓慢。对于向量中的每个元素,CPU 将不得不为其请求内存并且无法有效地使用其缓存。
        • 缺点:您必须使用std::unique_ptr 来拥有内存,并且很可能清楚地分配每个对象。分配大量不同的对象很慢。

        默认选择应该是std::vector&lt;MyClass&gt;。这是迄今为止最简单的,适用于大多数情况。通常当我需要对这些对象的引用时,我倾向于在向量中使用索引,只要中间没有删除任何元素就可以稳定。

        【讨论】:

          【解决方案5】:

          我听说 C++ 中不推荐使用指针,但我不明白为什么。

          指针比普通对象更难处理。并不是说它们完全不好,只是几乎在每个指针用例中都有很多更好的选择。

          例如,假设您想在调用者函数中修改一个函数参数。在 C 语言中,您会使用指针:

          void func(int* someMumber) {
              *somenumber = 3; // modifies value in caller
          }
          

          在 C++ 中,这被替换为通过引用传递:

          void func(int& someMumber) {
              somenumber = 3; // modifies value in caller
          }
          

          这样,不需要处理混乱的语法,也不必担心传递一个错误的指针等。无论调用者传递给我们什么,这都应该始终有效。^1

          另一个例子是假设你想要动态分配的数据:

          int* dynamicArray = new int[size];
          

          当你完成它时,你必须记住delete它:

          delete [] dynamicArray;
          

          这可能会变得一团糟,然后您必须跟踪您已删除和未删除的内容。然后,如果您不删除某些内容,则会造成内存泄漏。

          C++ 对此有更好的解决方案:std::vector。然后我可以在需要的时候向数组中添加尽可能多的元素:

          std::vector<int> dynamicArray;
          
          // later, when I need to store a value
          dynamicArray.push_back(someValue);
          

          无需担心数据泄露或类似情况。它会在完成后自动释放。

          出于其他原因需要指针吗?试试smart pointer

          std::unique_ptr<int> ptr = new int;
          

          智能指针会自动释放数据,您无需担心。它只会让你的生活更轻松。另外,您可以在需要时将它们转换为“原始”指针。

          所以并不是它们不好,只是它们被其他东西淘汰了。因此,不推荐使用它们。

          当然,为了更好的性能,我应该使用类对象指针的向量?

          编译器会为您优化很多,所以不必担心这一点。另一方面,使用指针会让你自己变得更加困难。您可能需要考虑上述选项之一。

          或者是否可以创建类对象的引用向量?

          不确定这是否直接可行,但使用std::reference_wrapper 可能是可行的。

          所以我的问题是:

          这些方式有什么区别?

          什么是创建类对象向量的最佳优化?

          最大的不同是,如果你将一个局部变量的地址推到你的指针向量中,并且它超出了范围,你的向量将被垃圾填充。要正确执行此操作,您将需要分配一个新对象。然后你将不得不删除它。这只是更多的工作。

          如果您使用引用向量,您将遇到与使用局部变量地址相同类型的范围问题。

          如果您按值存储它,您将不必担心这些。

          同样,不要担心优化。编译器会为你解决这个问题。如果您试图将其作为一种优化来做,那么您只是在为自己做更多的工作。


          1:实际上,调用者无法将文字传递给我们,because non-const references can't bind to literals。但这不是重点。

          【讨论】:

          • 旁注:如果编译为 C++14 或更高版本,则更喜欢 std::unique_ptr&lt;int&gt; ptr = std::make_unique&lt;int&gt;(); 而不是 std::unique_ptr&lt;int&gt; ptr = new int;。它关闭了一些可能的漏洞,例如构造 unique_pr 的失败导致 newed int 仍然存在并泄漏。
          • 好点。我仍然习惯 C++ 11。有机会我会编辑,或者随时编辑和添加。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-08-04
          • 1970-01-01
          • 2013-11-21
          • 1970-01-01
          • 1970-01-01
          • 2013-07-19
          • 2011-05-15
          相关资源
          最近更新 更多