C++:何时使用指针?

16

在阅读了一些教程之后,我得出的结论是应该始终使用对象指针。但是在阅读QT教程时(http://zetcode.com/gui/qt4/painting/),我也看到了一些例外情况,其中QPaint对象是在堆栈上创建的。所以现在我很困惑,什么时候应该使用指针?


QT教程在堆栈上创建QPaint对象,因为在编译时可以确定QPaint对象的寿命,并且完全由一对匹配的{}限制。 - Thomas L Holaday
对于QPen、QPainter和QApplication同样适用。 - Thomas L Holaday
关于指针需要了解的重要信息是,在C++中我们现在有一些安全实现指针的方法,比如unique_ptr或weak_ptr。当你需要使用指针时,你需要使用它们(除了在低级代码上有一些罕见的例外情况)。 - MokaT
14个回答

42

如果您不知道何时应该使用指针,那就不要使用它们。

当您需要使用指针时,情况会变得明显,每种情况都是不同的。很难简洁地总结何时应该使用它们。不要养成“始终为对象使用指针”的习惯,这肯定是错误的建议。


6
这不是一个答案。这个网站的目的是帮助人们学习更多,而不是在你无法表达思想时将其驳回。 - A.R.
@A.R. 希望我让 jamolkhon 学会了不要总是使用指针 - 看看他的问题的第一句话。我考虑了上下文,而不仅仅是标题。此外,其他问题列出了一些用途。 - CiscoIPPhone
1
@A.R. 我也是这么认为。但是考虑上下文可以帮助你创建一个比仅仅查看包含问题的句子更有用的答案 - 特别是如果其他人已经解决了这个问题。 - CiscoIPPhone
1
@A.R. 没必要这么刻薄。其他人认为我的回答很有帮助,如果你不这样认为,那也没关系,只需点个踩然后离开即可。 - CiscoIPPhone
1
@A.R. OP得出结论,他应该始终使用指针,我认为我的答案对这种特定情况很有帮助。我已经看到过很多次,即使在专业的代码库中,人们在没有必要的情况下也使用指针。我的建议是不要总是使用它们,只有在需要它们的情况下才会变得明显。它不能应用于所有问题 - 也许只适用于那些已经得出结论的问题,即应该始终使用某些可能会有害的东西。 - CiscoIPPhone
显示剩余3条评论

21

使用指针的主要原因:

  1. 控制对象生命周期;
  2. 无法使用引用(例如,您想在向量中存储不可复制的内容);
  3. 必须向某些第三方函数传递指针;
  4. 可能是出于某些优化原因,但我不确定。

4
我认为指针最重要的一点是控制对象的生命周期。 - Gustavo Muenz
第二点特别适用于Qt,因为大多数Qt图形相关对象都不可复制。 - Arnold Spence
@caparcode,事实上,QObjects是不可复制的。这包括GUI元素和极少数图形类,以及其他一些东西。其他类也可能因为某种原因而不可复制。 - strager
引用可以存储在向量中:std::vector< boost::optional<T&> >。 - Vadim Ferderer

15

我不清楚你的问题是关于指向对象 vs 基于栈的对象,还是指向对象 vs 对象引用。也有一些用途不属于这两个范畴。

关于基于栈 vs 指针,似乎上面已经涉及到了。有几个原因,其中最明显的是对象的生存期。

关于引用 vs 指针,请尽可能使用引用,但有些事情只能使用指针,例如(有许多用途):

  • 遍历数组中的元素(例如,在标准数组[]上行进)
  • 当被调用的函数通过指针分配并返回某些东西时

最重要的是,指针(和引用,与自动/基于栈和静态对象相反)支持多态性。基类的指针实际上可以指向派生类。这是C++中支持面向对象行为的基本特征。


3
+1 那个最后加粗的段落,因为我自己不能再点赞了。 - David Thornley
但是你可以在堆栈上分配一些东西,然后通过传递引用或指针到您的函数/容器/任何其他地方,在以后使用它进行多态操作。存储期和多态性是正交概念。然而,人们继续回答有关为什么可能通过指针分配的问题,并引用多态性作为优势,而从未解释两者之间的关系(它们没有关系)。 - underscore_d

10

首先,这个问题是错误的:困境不在于指针和栈之间,而在于堆和栈之间。您可以在栈上拥有一个对象并传递该对象的指针。我假设您真正想问的是应该声明类的指针还是类的实例。

答案取决于您想要对该对象执行什么操作。如果控制离开函数后对象必须存在,则必须使用指针并在堆上创建对象。例如,当您的函数必须返回指向创建的对象的指针或将对象添加到在调用函数之前创建的列表中时,您将执行此操作。

另一方面,如果对象局限于函数本身,则最好在栈上使用它。这使编译器能够在控制离开函数时调用析构函数。


4
哪些教程是指?实际上,规则是你只有在绝对必要的情况下才应该使用指针,这种情况非常罕见。你需要阅读一本好的C++书籍,比如由Koenig & Moo编写的Accelerated C++编辑说明:为了澄清一点 - 两个不使用指针的例子(这里使用字符串作为样例 - 对于任何其他类型都是相同的):
class Person {
   public:
      string name;       // NOT string * name;
   ...
};


void f() {
   string value;        // NOT string * value
   // use vvalue
}

堆栈的限制如何? - Jamol
那会有什么限制呢? - anon
指针是一种工具。仅在必须时使用它们的限制太严格了。通过适当的设计考虑,它们可以成为非常易于维护和强大的设计的关键。 - Nate
@nate 没有人在暗示其他的意思。 - anon
在15年的编程生涯中,我从未遇到过除了愚蠢的递归错误之外的栈溢出问题。 - Rob K
显示剩余2条评论

4

在以下场景中,您通常需要使用指针:

  1. 您需要一个包含不同类对象的集合(大多数情况下它们将具有共同的基类)。

  2. 您需要一个栈分配的对象集合,如果太大就可能导致堆栈溢出。

  3. 您需要能够快速重新排列对象的数据结构 - 如链接列表、树形结构或类似物。

  4. 您需要一些复杂的对象生命周期管理逻辑。

  5. 您需要一种数据结构,允许从一个对象直接导航到另一个对象 - 如链接列表、树形结构或任何其他图形。


在情况(1)中,共同基础的需求是毫无疑问的。而在情况(2)中,唯一适用的“集合”将是C风格数组。 - anon
对于情况(1),这并非必需,您可能希望存储一个void*数组,以便只需调用free()即可 - 这不是C++的做法,但也许可以。对于情况(2),我指的是指针数组与实际对象数组之间的区别。如果对象非常大,则存储指针可以节省大量堆栈空间。 - sharptooth

2

除了其他人提到的点(特别是关于控制对象生命周期的),如果您需要处理NULL对象,则应使用指针而不是引用。虽然可以通过类型转换创建NULL引用,但通常这是一个不好的想法。


这实际上是一个相当糟糕的想法。 - Dave Van den Eynde

1

在这种情况下,我实际上使用指针:

class Foo
{
    Bar* bar;

    Foo(Bar& bar) : bar(&bar) { }

    Bar& Bar() const { return *bar; }
};

在此之前,我使用了从构造函数初始化的引用成员,但是编译器在创建复制构造函数、赋值运算符等方面遇到了问题。
Dave

1

使用指针涉及到两个正交的事情:

  1. 动态分配。通常情况下,当对象的生命周期超出创建它的作用域时,应该进行动态分配。这样的对象是一种资源,其所有者必须明确指定(通常是某种智能指针)。

  2. 通过地址访问(无论对象是如何创建的)。在这种情况下,指针并不意味着所有权。这种访问可能需要在以下情况下进行:

    • 某些已经存在的接口要求这样做。
    • 需要建模可能为空的关联。
    • 需要避免复制大型对象或根本无法复制,但不能使用引用(例如stl集合)。

#1和#2可以以不同的配置出现,例如您可以想象通过指针访问动态分配的对象,但是这样的对象也可以通过引用传递给某个函数。您还可以获得指向堆栈上创建的某个对象的指针等。


"两个正交的事情" - 最终,有人这么说了! 参考 我想要补充第二点,通过引用/指针(=地址)访问使得多态成为可能,无论对象是如何分配的 - 这与我看到的大量帖子暗示的令人困惑的理由相反,即动态分配的有效原因是授予多态行为 - 这不是必需的,也不是一个论点,完全无关。 - underscore_d

1

对于大部分的代码,使用可良好复制对象的按值传递是正确的方法。

如果速度真的很重要,可以在可能的情况下使用按引用传递,最后再考虑使用指针。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接