为什么我应该使用指针而不是对象本身?

新手上路,请多包涵

我来自 Java 背景并开始使用 C++ 中的对象。但是我想到的一件事是人们经常使用指向对象的指针而不是对象本身,例如这个声明:

 Object *myObject = new Object;

而不是:

 Object myObject;

或者不使用函数,比如 testFunc() ,如下所示:

 myObject.testFunc();

我们必须写:

 myObject->testFunc();

但我不明白我们为什么要这样做。我认为这与效率和速度有关,因为我们可以直接访问内存地址。我对吗?

原文由 gEdringer 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 763
2 个回答

很遗憾,您经常看到动态分配。这只是表明有多少糟糕的 C++ 程序员。

从某种意义上说,你有两个问题捆绑在一起。首先是我们什么时候应该使用动态分配(使用 new )?第二个是我们什么时候应该使用指针?

重要的带回家的信息是,您应该 始终使用适当的工具来完成工作。在几乎所有情况下,都有比执行手动动态分配和/或使用原始指针更合适和更安全的方法。

动态分配

在您的问题中,您已经演示了两种创建对象的方法。主要区别在于对象的存储持续时间。在块内执行 Object myObject; 时,创建的对象具有自动存储持续时间,这意味着它会在超出范围时自动销毁。当您执行 new Object() 时,该对象具有动态存储持续时间,这意味着它会一直保持活动状态,直到您明确 delete 它。您应该只在需要时使用动态存储持续时间。也就是说, 您应该 始终 更喜欢在可能的情况下创建具有自动存储持续时间的对象

您可能需要动态分配的主要两种情况:

  1. 您需要该对象比当前范围更长- 该特定内存位置的特定对象,而不是它的副本。如果您可以复制/移动对象(大多数情况下应该如此),您应该更喜欢自动对象。
  2. 您需要分配大量内存,这可能很容易填满堆栈。如果我们不必关心这个就好了(大多数时候你不应该关心),因为它确实超出了 C++ 的范围,但不幸的是,我们必须处理系统的现实我们正在开发。

当您确实需要动态分配时,您应该将其封装在智能指针或其他执行 RAII 的类型(如标准容器)中。智能指针提供动态分配对象的所有权语义。例如,看看 std::unique_ptrstd::shared_ptr 。如果你适当地使用它们,你几乎可以完全避免执行你自己的内存管理(参见 零规则)。

指针

但是,除了动态分配之外,原始指针还有其他更一般的用途,但大多数都有您应该喜欢的替代方案。和以前一样, 除非你真的需要指针,否则总是更喜欢替代方案

  1. 您需要参考语义。有时您想使用指针传递一个对象(不管它是如何分配的),因为您希望传递它的函数能够访问该特定对象(而不是它的副本)。但是,在大多数情况下,您应该更喜欢引用类型而不是指针,因为这正是它们的设计目的。请注意,这不一定是关于将对象的生命周期延长到当前范围之外,如上面的情况 1。和以前一样,如果您可以传递对象的副本,则不需要引用语义。

  2. 你需要多态性。您只能通过指针或对对象的引用以多态方式(即根据对象的动态类型)调用函数。如果这是您需要的行为,那么您需要使用指针或引用。同样,参考文献应该是首选。

  3. 您希望通过在省略对象时允许传递 nullptr 来表示该对象是可选 的。如果它是一个参数,您应该更喜欢使用默认参数或函数重载。否则,您最好使用封装此行为的类型,例如 std::optional (在 C++17 中引入 - 对于早期的 C++ 标准,请使用 boost::optional )。

  4. 您希望解耦编译单元以缩短编译时间。指针的有用属性是您只需要指向类型的前向声明(要实际使用该对象,您需要一个定义)。这允许您解耦编译过程的各个部分,这可能会显着缩短编译时间。请参阅 Pimpl 成语

  5. 您需要与 C 库 或 C 样式库进行交互。此时,您被迫使用原始指针。你能做的最好的事情是确保你只在最后一刻松开你的原始指针。您可以从智能指针获取原始指针,例如,通过使用其 get 成员函数。如果一个库为您执行了一些分配,它希望您通过句柄解除分配,您通常可以使用自定义删除器将句柄包装在智能指针中,该删除器将适当地解除分配对象。

原文由 Joseph Mansfield 发布,翻译遵循 CC BY-SA 4.0 许可协议

tl; dr:不要“使用指针而不是对象本身”(通常)

你问为什么你应该更喜欢指针而不是对象本身。嗯,你不应该,作为一般规则。

现在,这条规则确实有多个例外,其他答案已经说明了它们。问题是,这些天来,许多这些例外不再有效!让我们考虑 接受的答案 中列出的例外情况:

  1. 您需要参考语义。

如果您需要引用语义,请使用 引用,而不是指针;请参阅 @ST3 的答案答案。事实上,有人可能会争辩说,在 Java 中,您传递的通常是引用。

  1. 你需要多态性。

如果您知道您将使用的类集,通常您可以使用 std::variant<ClassA, ClassB, ClassC> (参见 此处 的描述)并使用访问者模式对它们进行操作。现在,诚然,C++ 的变体实现并不是最漂亮的。但我通常更喜欢它而不是用指针弄脏。

你想表示一个对象是可选的

绝对不要为此使用指针。您有 std::optional ,与 std::variant 不同,它非常方便。改用那个。 nullopt 是一个空(或“null”)可选。而且 - 它不是一个指针。

您希望解耦编译单元以缩短编译时间。

您也可以使用引用而不是指针来实现这一点。要在一段代码中使用 Object& ,说 class Object; 就足够了,即使用 前向声明

您需要与 C 库或 C 样式库进行交互。

是的,好吧,如果您使用已经使用指针的代码,那么-您必须自己使用指针,无法解决这个问题:-(并且C没有引用。

此外,有些人可能会告诉您使用指针来避免复制对象。好吧,由于 返回值和命名返回值优化(RVO 和 NRVO) ,这对于返回值来说并不是真正的问题。在其他情况下 - 引用避免复制就好了。

不过,底线规则仍然与公认的答案相同:仅当您有充分的理由需要指针时才使用指针。


PS - 如果你确实需要一个指针,你仍然应该 避免直接使用 newdelete智能指针 可能会更好地为您服务 - 它会自动释放(不像 Java,但仍然如此)。

原文由 einpoklum 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题