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

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

Object *myObject = new Object;

而不是:

Object myObject;

或者用一个函数,比如说testFunc() ,就像这样:

myObject.testFunc();

我们必须写:

myObject->testFunc();

但我无法弄清楚为什么我们应该这样做。 因为我们可以直接访问内存地址,所以我会假设它与效率和速度有关。 我对吗?


非常不幸的是,你经常看到动态分配。 这只是表明有多少不好的C ++程序员。

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

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

动态分配

在你的问题中,你已经演示了两种创建对象的方式。 主要区别是对象的存储时间。 在做Object myObject; 在一个块内部,该对象被创建为自动存储持续时间,这意味着该对象在超出范围时将被自动销毁。 当您执行new Object() ,该对象具有动态存储持续时间,这意味着它保持活动状态直到您明确delete它。 您只需在需要时使用动态存储持续时间。 也就是说, 当你可以的时候你应该总是喜欢创建自动存储持续时间的对象

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

  • 您需要该对象超出当前范围 - 该特定内存位置的特定对象,而不是它的副本。 如果您可以复制/移动对象(大部分时间您应该是),您应该更喜欢自动对象。
  • 你需要分配很多内存 ,这很容易填满堆栈。 如果我们不必关心自己(大多数情况下你不需要),这将是很好的,因为它实际上超出了C ++的范围,但不幸的是我们必须处理系统的实际情况。正在开发。
  • 当你绝对需要动态分配时,你应该把它封装在一个智能指针或其他一些执行RAII的类型(如标准容器)中。 智能指针提供动态分配对象的所有权语义。 例如,看看std::unique_ptrstd::shared_ptr 。 如果适当地使用它们,几乎可以完全避免执行自己的内存管理(请参阅零规则)。

    指针

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

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

  • 你需要多态 。 你只能通过指针或对象的引用多态调用函数(也就是说,根据对象的动态类型)。 如果这是你需要的行为,那么你需要使用指针或引用。 同样,参考应该是首选。

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

  • 你想分离编译单元来提高编译时间 。 指针的有用属性是您只需要指向类型的前向声明(实际使用该对象,您需要定义)。 这使您可以分离编译过程的某些部分,这可能会显着缩短编译时间。 见Pimpl成语。

  • 您需要与C库或C风格的库进行交互 。 在这一点上,你不得不使用原始指针。 你可以做的最好的事情是确保你只在最后时刻让你的指针松动。 您可以从智能指针中获取原始指针,例如,使用其get成员函数。 如果一个库为你执行一些你希望通过一个句柄释放的分配,你通常可以用一个自定义的删除器将这个句柄包装在一个智能指针中,该指针将适当地释放该对象。


  • 指针有很多用例。

    多态行为 。 对于多态类型,使用指针(或引用)来避免切片:

    class Base { ... };
    class Derived : public Base { ... };
    
    void fun(Base b) { ... }
    void gun(Base* b) { ... }
    void hun(Base& b) { ... }
    
    Derived d;
    fun(d);    // oops, all Derived parts silently "sliced" off
    gun(&d);   // OK, a Derived object IS-A Base object
    hun(d);    // also OK, reference also doesn't slice
    

    引用语义并避免复制 。 对于非多态类型,指针(或引用)将避免复制潜在的昂贵对象

    Base b;
    fun(b);  // copies b, potentially expensive 
    gun(&b); // takes a pointer to b, no copying
    hun(b);  // regular syntax, behaves as a pointer
    

    请注意,C ++ 11具有移动语义,可以避免将许多昂贵的对象复制到函数参数和返回值中。 但是使用指针肯定会避免这些,并且会允许同一对象上有多个指针(而对象只能从一次移动)。

    资源获取 。 使用new运算符创建指向资源的指针是现代C ++中的反模式 。 使用特殊资源类(标准容器之一)或智能指针std::unique_ptr<>std::shared_ptr<> )。 考虑:

    {
        auto b = new Base;
        ...       // oops, if an exception is thrown, destructor not called!
        delete b;
    }
    

    {
        auto b = std::make_unique<Base>();
        ...       // OK, now exception safe
    }
    

    原始指针只能用作“视图”,并不以任何方式参与所有权,无论是通过直接创建还是隐式地通过返回值。 另请参阅C ++ FAQ中的Q&A

    更细粒度的生命期控制每次共享指针被复制时(例如作为函数参数),指向的资源都会保持活动状态。 常规对象(不是由new创建的,无论是直接由您还是由资源类内部创建的)在超出范围时被销毁。


    这个问题有很多很好的答案,包括前向声明,多态等的重要用例,但我觉得你的问题的“灵魂”的一部分没有得到回答 - 即不同语法在Java和C ++中的含义。

    我们来看看比较两种语言的情况:

    Java的:

    Object object1 = new Object(); //A new object is allocated by Java
    Object object2 = new Object(); //Another new object is allocated by Java
    
    object1 = object2; 
    //object1 now points to the object originally allocated for object2
    //The object originally allocated for object1 is now "dead" - nothing points to it, so it
    //will be reclaimed by the Garbage Collector.
    //If either object1 or object2 is changed, the change will be reflected to the other
    

    与此最接近的是:

    C ++:

    Object * object1 = new Object(); //A new object is allocated on the heap
    Object * object2 = new Object(); //Another new object is allocated on the heap
    delete object1;
    //Since C++ does not have a garbage collector, if we don't do that, the next line would 
    //cause a "memory leak", i.e. a piece of claimed memory that the app cannot use 
    //and that we have no way to reclaim...
    
    object1 = object2; //Same as Java, object1 points to object2.
    

    让我们看看另一种C ++方式:

    Object object1; //A new object is allocated on the STACK
    Object object2; //Another new object is allocated on the STACK
    object1 = object2;//!!!! This is different! The CONTENTS of object2 are COPIED onto object1,
    //using the "copy assignment operator", the definition of operator =.
    //But, the two objects are still different. Change one, the other remains unchanged.
    //Also, the objects get automatically destroyed once the function returns...
    

    最好的办法就是 - 或多或少 - Java(隐式地)处理指向对象的指针,而C ++可以处理指向对象的指针或对象本身。 有一些例外情况 - 例如,如果声明Java“原始”类型,则它们是被复制的实际值,而不是指针。 所以,

    Java的:

    int object1; //An integer is allocated on the stack.
    int object2; //Another integer is allocated on the stack.
    object1 = object2; //The value of object2 is copied to object1.
    

    也就是说,使用指针不一定是处理事物的正确或错误的方式; 但其他答案已经令人满意地报道了这一点。 然而,总体思路是,在C ++中,对对象的生命周期以及它们的生活位置有更多的控制权。

    回到顶端 - Object * object = new Object()构造实际上是最接近典型Java(或C#)的语义。

    链接地址: http://www.djcxy.com/p/829.html

    上一篇: Why should I use a pointer rather than the object itself?

    下一篇: Why is processing a sorted array slower than an unsorted array?