首页 文章

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

提问于
浏览
1391

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

Object *myObject = new Object;

而不是:

Object myObject;

或者不是使用函数,让我们说 testFunc() ,像这样:

myObject.testFunc();

我们要写:

myObject->testFunc();

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

22 回答

  • 1392

    从技术上讲,这是一个内存分配问题,但是这里有两个更实际的方面 . 它与两件事有关:1)范围,当你定义一个没有指针的对象时,你将无法再在定义代码块后访问它,而如果用“new”定义一个指针,那么你可以从任何指向此内存的指针访问它,直到在同一指针上调用“delete” . 2)如果要将参数传递给函数,则需要传递指针或引用以提高效率 . 传递一个Object然后复制该对象,如果这是一个使用大量内存的对象,那么这可能是CPU消耗的(例如,复制一个充满数据的向量) . 传递指针时,所有传递的都是一个int(取决于实现,但大多数都是一个int) .

    除此之外,您需要了解“new”在堆上分配需要在某个时刻释放的内存 . 当您不必使用“new”时,我建议您在“堆栈”中使用常规对象定义 .

  • 162

    假设你有 class A 包含 class B 当你想在 class A 之外调用 class B 的某个函数时,你只需要获得一个指向这个类的指针,你可以做任何你想做的事情,它也会改变 class Aclass B 的上下文 .

    但要小心动态对象

  • -5

    With pointers

    • 可以直接与内存通信 .

    • 可以通过操作指针来防止程序的大量内存泄漏 .

  • 6

    使用指针的一个原因是与C函数接口 . 另一个原因是节省内存;例如:代替传递包含大量数据的对象并且具有处理器密集型复制构造函数的函数,只需传递指向对象的指针,节省内存和速度,尤其是在循环中,在这种情况下,引用会更好,除非你使用的是C风格的数组 .

  • 118

    C为您提供了三种传递对象的方法:通过指针,引用和值 . Java限制你使用后者(唯一的例外是原始类型,如int,boolean等) . 如果你想使用C不仅仅是一个奇怪的玩具,那么你最好先了解这三种方式之间的区别 .

    Java假装没有'who and when should destroy this?'这样的问题 . 答案是:垃圾收集器,伟大而可怕 . 然而,它无法提供100%的内存泄漏保护(是的,java can leak memory) . 实际上,GC会给你一种虚假的安全感 . 您的SUV越大,您到疏散器的路程越长 .

    C让您与对象的生命周期管理面对面 . 好吧,有办法处理它(smart pointers系列,Qt中的QObject等等),但它们都不能像GC一样以'fire and forget'方式使用:你应该 always 记住内存处理 . 你不仅要关心破坏一个物体,还必须避免不止一次地破坏同一个物体 .

    还不害怕吗?好的:循环引用 - 自己处理它们,人类 . 并且记住:精确地杀死每个物体一次,我们C运行时不喜欢那些捣乱尸体的人,只留下死者 .

    所以,回到你的问题 .

    当你通过值而不是通过指针或引用传递对象时,你复制了对象(整个对象,无论是几个字节还是一个巨大的数据库转储 - 你足够聪明,可以避免后者,不是'你呢?)每次你做'=' . 要访问对象的成员,请使用“ . ” (点) .

    当您通过指针传递对象时,您只复制几个字节(32位系统上4个,64位上8个),即 - 此对象的地址 . 为了向所有人展示这一点,您在访问成员时使用这个花哨的' - >'运算符 . 或者您可以使用'*'和' . '的组合 .

    当您使用引用时,您将获得假装为值的指针 . 它是一个指针,但您可以通过“ . ”访问成员 .

    并且,再次吹嘘你的想法:当你用逗号分隔几个变量时,那么(看着手):

    • 类型给予每个人

    • 值/指针/引用修饰符是个体的

    例:

    struct MyStruct
    {
        int* someIntPointer, someInt; //here comes the surprise
        MyStruct *somePointer;
        MyStruct &someReference;
    };
    
    MyStruct s1; //we allocated an object on stack, not in heap
    
    s1.someInt = 1; //someInt is of type 'int', not 'int*' - value/pointer modifier is individual
    s1.someIntPointer = &s1.someInt;
    *s1.someIntPointer = 2; //now s1.someInt has value '2'
    s1.somePointer = &s1;
    s1.someReference = s1; //note there is no '&' operator: reference tries to look like value
    s1.somePointer->someInt = 3; //now s1.someInt has value '3'
    *(s1.somePointer).someInt = 3; //same as above line
    *s1.somePointer->someIntPointer = 4; //now s1.someInt has value '4'
    
    s1.someReference.someInt = 5; //now s1.someInt has value '5'
                                  //although someReference is not value, it's members are accessed through '.'
    
    MyStruct s2 = s1; //'NO WAY' the compiler will say. Go define your '=' operator and come back.
    
    //OK, assume we have '=' defined in MyStruct
    
    s2.someInt = 0; //s2.someInt == 0, but s1.someInt is still 5 - it's two completely different objects, not the references to the same one
    
  • 12

    指针有很多用例 .

    Polymorphic behavior . 对于多态类型,指针(或引用)用于避免切片:

    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
    

    Reference semantics and avoiding copying . 对于非多态类型,指针(或引用)将避免复制可能昂贵的对象

    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具有移动语义,可以避免昂贵对象的许多副本进入函数参数和返回值 . 但是使用指针肯定会避免使用指针,并允许在同一个对象上使用多个指针(而对象只能从一次移动) .

    Resource acquisition . 使用 new 运算符创建指向资源的指针是现代C中的 anti-pattern . 使用特殊资源类(标准容器之一)或 smart pointerstd::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
    }
    

    原始指针应仅用作"view",并且不以任何方式涉及所有权,无论是直接创建还是隐式返回值 . 另见this Q&A from the C++ FAQ .

    More fine-grained life-time control 每次复制共享指针(例如作为函数参数)时,它指向的资源都将保持活动状态 . 超出范围时,常规对象(由 new 创建,不是由您直接创建,也不是由资源类创建)会被销毁 .

  • 23

    在C中,在堆栈上分配的对象(使用块中的 Object object; 语句)将仅存在于它们声明的范围内 . 当代码块完成执行时,声明的对象将被销毁 . 而如果你使用 Object* obj = new Object() 在堆上分配内存,它们将继续存在于堆中,直到你调用 delete obj 为止 .

    当我想在不仅在声明/分配它的代码块中使用该对象时,我会在堆上创建一个对象 .

  • 0

    这个问题有许多优秀的答案,包括前向声明,多态等的重要用例,但我觉得你的问题的“灵魂”的一部分没有得到回答 - 即不同的语法在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#)的语义 .

  • 5

    我将包括一个重要的指针用例 . 当您在基类中存储一些对象时,它可能是多态的 .

    Class Base1 {
    };
    
    Class Derived1 : public Base1 {
    };
    
    
    Class Base2 {
      Base *bObj;
      virtual void createMemerObects() = 0;
    };
    
    Class Derived2 {
      virtual void createMemerObects() {
        bObj = new Derived1();
      }
    };
    

    所以在这种情况下你不能将bObj声明为直接对象,你必须有指针 .

  • 75

    “必要性是发明的母亲 . ”我想指出的最重要的区别是我自己的编码经验的结果 . 有时您需要将对象传递给函数 . 在这种情况下,如果你的对象属于一个非常大的类,那么将它作为一个对象传递它将复制它的状态(你可能不想要它......并且可能过大)因此导致复制对象的开销 . 而指针是固定的4字节大小(假设32位) . 上面已经提到了其他原因......

  • 19

    使用指针对象有很多好处 -

    • 效率(正如您已经指出的那样) . 将对象传递给函数意味着创建对象的新副本 .

    • 使用第三方库中的对象 . 如果你的对象属于第三方代码而且作者只想通过指针使用他们的对象(没有复制构造函数等),那么传递这个对象的唯一方法就是使用指针 . 通过 Value 传递可能会导致问题 . (深拷贝/浅拷贝问题) .

    • 如果对象拥有一个资源,并且您希望所有权不应该与其他对象一起使用 .

  • 5

    指针直接引用对象的内存位置 . Java没有这样的东西 . Java具有通过哈希表引用对象位置的引用 . 你不能用这些引用做Java之类的指针算法 .

    要回答你的问题,这只是你的偏好 . 我更喜欢使用类似Java的语法 .

  • 64

    你不应该 . 可悲的是,人们(很多人)都是出于无知而写出来的 .

    有时动态分配有它的位置,但在你给出的例子中,它是错误的 .

    如果你想考虑效率,那就更糟了,因为它没有充分的理由引入间接 . 这种编程更慢,更容易出错 .

  • 19

    那么主要的问题是 Why should I use a pointer rather than the object itself? 我的回答,你应该(几乎)从不使用指针代替对象,因为C有references,它比指针更安全,并保证与指针相同的性能 .

    您在问题中提到的另一件事:

    Object *myObject = new Object;
    

    怎么样它有用吗?它创建 Object 类型的指针,分配内存以适应一个对象并调用默认构造函数,听起来不错,对吧?但实际上它并不是那么好,如果你动态分配内存(使用关键字 new ),你还必须手动释放内存,这意味着你应该在代码中:

    delete myObject;
    

    这会调用析构函数并释放内存,看起来很容易,但是在大项目中可能很难检测到一个线程是否释放了内存,但为了这个目的你可以尝试shared pointers,这些会略微降低性能,但是使用它们要容易得多 .


    现在一些介绍已经结束并回过头来回答问题 .

    在函数之间传输数据时,可以使用指针而不是对象来获得更好的性能 .

    看一下,你有 std::string (它也是对象)并且它包含很多数据,例如大XML,现在你需要解析它,但为此你有函数 void foo(...) ,它可以用不同的方式声明:

    • void foo(std::string xml); 在这种情况下,您将所有数据从变量复制到函数堆栈,这需要一些时间,因此您的性能会很低 .

    • void foo(std::string* xml); 在这种情况下,您将传递指向对象的指针,速度与传递 size_t 变量相同,但是此声明容易出错,因为您可以传递 NULL 指针或无效指针 . 指针通常用于 C ,因为它没有引用 .

    • void foo(std::string& xml); 这里你传递引用,基本上它与传递指针相同,但编译器做了一些东西,你不能传递无效的引用(实际上它可能创建带有无效引用的情况,但它是欺骗编译器) .

    • void foo(const std::string* xml); 这里与第二个相同,只是指针值无法更改 .

    • void foo(const std::string& xml); 这与第三个相同,但无法更改对象值 .

    我还想提一下,无论您选择哪种分配方式(使用 new 或常规),您都可以使用这5种方式传递数据 .


    另外要提一下,当你以常规方式创建对象时,你在堆栈中分配内存,但是当你使用 new 创建它时,你会分配堆 . 分配堆栈要快得多,但它对于真正大的数据数组来说要小一些,所以如果你需要大对象你应该使用堆,因为你可能会得到堆栈溢出,但通常这个问题是使用STL containers来解决的,并记住 std::string 也是容器,有些人忘了:)

  • -1
    Object *myObject = new Object;
    

    执行此操作将创建对Object(在堆上)的引用,必须明确删除该对象以避免 memory leak .

    Object myObject;
    

    执行此操作将创建自动类型(在堆栈上)的对象(myObject),当对象(myObject)超出范围时,该对象将自动删除 .

  • 2

    使用指针的另一个好理由是forward declarations . 在一个足够大的项目中,它们可以真正加快编译时间 .

  • 0

    这已经详细讨论过,但在Java中,一切都是指针 . 它没有区分堆栈和堆分配(所有对象都在堆上分配),因此您没有意识到您正在使用指针 . 在C中,您可以将两者混合,具体取决于您的内存要求 . 性能和内存使用在C(duh)中更具确定性 .

  • 0

    已有很多优秀的答案,但让我举一个例子:

    我有一个简单的Item类:

    class Item
        {
        public: 
          std::string name;
          int weight;
          int price;
        };
    

    我制作了一个矢量来容纳一堆它们 .

    std::vector<Item> inventory;

    我创建了一百万个Item对象,然后将它们推回到向量上 . 我按名称对矢量进行排序,然后对特定项目名称进行简单的迭代二进制搜索 . 我测试程序,完成执行需要8分钟 . 然后我像这样改变我的库存矢量:

    std::vector<Item *> inventory;

    ...并通过new创建我的百万Item对象 . 我对代码所做的唯一更改是使用指向Items的指针,除了我最后为内存清理添加的循环 . 该程序运行时间不到40秒,或者比速度增加10倍更好 . 编辑:代码是http://pastebin.com/DK24SPeW使用编译器优化它只显示我刚刚测试它的机器上增加了3.4倍,这仍然相当可观 .

  • 3

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

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

    重要的带回家信息是你应该 always use the appropriate tool for the job . 在几乎所有情况下,都比执行手动动态分配和/或使用原始指针更合适,更安全 .

    动态分配

    在您的问题中,您已经演示了两种创建对象的方法 . 主要区别在于对象的存储持续时间 . 在块中执行 Object myObject; 时,将使用自动存储持续时间创建对象,这意味着当它超出范围时将自动销毁 . 当您执行 new Object() 时,该对象具有动态存储持续时间,这意味着它保持活着,直到你明确 delete 它 . 您应该只在需要时使用动态存储持续时间 . 那就是 you should always prefer creating objects with automatic storage duration when you can .

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

    • You need the object to outlive the current scope - 该特定内存位置的特定对象,而不是它的副本 . 如果你可以复制/移动对象(大多数时候你应该),你应该更喜欢自动对象 .

    • You need to allocate a lot of memory ,这可能很容易填满堆栈 . 如果我们没有这样做会很好,因为's really outside the purview of C++, but unfortunately we have to deal with the reality of the systems we'正在开发 .

    当您绝对需要动态分配时,您应该将其封装在智能指针或执行RAII的其他类型(如标准容器)中 . 智能指针提供动态分配对象的所有权语义 . 例如,看一下std::unique_ptrstd::shared_ptr . 如果您正确使用它们,您几乎可以完全避免执行自己的内存管理(请参阅Rule of Zero) .

    指针

    但是,除了动态分配之外,还有其他更常用的原始指针,但大多数都有您应该选择的替代方案 . 和以前一样, always prefer the alternatives unless you really need pointers .

    • You need reference semantics . 有时你想使用指针传递一个对象(无论它是如何分配的),因为你想要为你设计的函数 . 注意,这不一定是将对象的生命周期延长到当前范围之外,如上面的情况1所示 . 和以前一样,如果你're okay with passing a copy of the object, you don't需要引用语义 .

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

    • You want to represent that an object is optional 允许在省略对象时传递 nullptr . 如果它是一个参数,您应该更喜欢使用默认参数或函数重载 . 否则,您应该更喜欢使用封装此行为的类型,例如 std::optional (在C17中引入 - 使用早期的C标准,使用 boost::optional ) .

    • You want to decouple compilation units to improve compilation time . 指针的有用属性是您只需要指向类型的前向声明(要实际使用该对象,您需要一个定义) . 这允许您解耦部分编译过程,这可能会显着缩短编译时间 . 见Pimpl idiom .

    • You need to interface with a C library 或C风格的图书馆 . 此时,您被迫使用原始指针 . 你能做的最好的事情就是确保你只让你的原始指针在最后一刻松动 . 您可以从智能指针获取原始指针,例如,使用其 get 成员函数 . 如果库为您执行某些分配,它希望您通过句柄释放,则通常可以使用自定义删除器将句柄包装在智能指针中,该删除器将适当地释放对象 .

  • 1

    但我无法弄清楚我们为什么要这样使用它?

    如果您使用以下内容,我将比较它在函数体内的工作原理:

    Object myObject;
    

    在函数内部,一旦此函数返回, myObject 将被销毁 . 因此,如果您不需要函数外的对象,这将非常有用 . 该对象将放在当前线程堆栈中 .

    如果你在函数体内写:

    Object *myObject = new Object;
    

    然后,当函数结束时, myObject 指向的Object类实例不会被销毁,并且分配在堆上 .

    现在,如果您是Java程序员,那么第二个示例更接近于java下的对象分配 . 这一行: Object *myObject = new Object; 相当于java: Object myObject = new Object(); . 不同的是,在java下myObject会被垃圾收集,而在c下它不会被释放,你必须在某处显式调用`delete myObject;'否则你会引入内存泄漏 .

    从c 11开始,您可以使用安全的动态分配方式: new Object ,通过在shared_ptr / unique_ptr中存储值 .

    std::shared_ptr<std::string> safe_str = make_shared<std::string>("make_shared");
    
    // since c++14
    std::unique_ptr<std::string> safe_str = make_unique<std::string>("make_shared");
    

    此外,对象通常存储在容器中,如map-s或vector-s,它们将自动管理对象的生命周期 .

  • 0

    前言

    与炒作相反,Java与C完全不同 . Java炒作机器希望您相信,因为Java具有类似C的语法,语言是相似的 . 事实并非如此 . 这种错误信息是Java程序员转向C并使用类似Java的语法而不了解其代码含义的原因之一 .

    我们继续前进

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

    相反,实际上 . The heap is much slower比堆栈,因为堆栈与堆相比非常简单 . 自动存储变量(也称为堆栈变量)一旦超出范围就会调用它们的析构函数 . 例如:

    {
        std::string s;
    }
    // s is destroyed here
    

    另一方面,如果你使用指针动态分配,必须手动调用其析构函数 . delete 为您调用此析构函数 .

    {
        std::string* s = new std::string;
    }
    delete s; // destructor called
    

    这与C#和Java中流行的 new 语法无关 . 它们用于完全不同的目的 .

    动态分配的好处

    1.您无需事先知道阵列的大小

    许多C程序员遇到的首要问题之一是,当他们接受来自用户的任意输入时,您只能为堆栈变量分配固定大小 . 您也无法更改数组的大小 . 例如:

    char buffer[100];
    std::cin >> buffer;
    // bad input = buffer overflow
    

    当然,如果您使用了 std::stringstd::string 在内部调整自身大小,这应该不是问题 . 但基本上这个问题的解决方案是动态分配 . 您可以根据用户的输入分配动态内存,例如:

    int * pointer;
    std::cout << "How many items do you need?";
    std::cin >> n;
    pointer = new int[n];
    

    旁注:许多初学者犯的一个错误就是使用可变长度数组 . 这是一个GNU扩展,也是Clang中的一个,因为它们反映了许多GCC的扩展 . 所以不应该依赖以下int arr [n] .

    因为堆比堆栈大得多,所以可以任意分配/重新分配他/她需要的内存,而堆栈有一个限制 .

    2.数组不是指针

    你问这个好处怎么样?一旦你理解了数组和指针背后的混乱/神话,答案就会变得清晰 . 通常认为它们是相同的,但它们不是 . 这个神话来自这样一个事实:指针可以像数组一样下标,并且因为数组在函数声明中衰减到顶层的指针 . 但是,一旦数组衰减到指针,指针就会丢失其 sizeof 信息 . 所以 sizeof(pointer) 将以字节为单位给出指针的大小,这通常是64位系统上的8个字节 .

    您不能分配给数组,只能初始化它们 . 例如:

    int arr[5] = {1, 2, 3, 4, 5}; // initialization 
    int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
                                 // be given by the amount of members in the initializer  
    arr = { 1, 2, 3, 4, 5 }; // ERROR
    

    另一方面,你可以用指针做任何你想做的事 . 不幸的是,因为指针和数组之间的区别是用Java和C#手动挥手的,所以初学者不理解它们之间的区别 .

    3.多态性

    Java和C#具有允许您将对象视为另一个对象的工具,例如使用 as 关键字 . 因此,如果有人想将 Entity 对象视为 Player 对象,则可以执行 Player player = Entity as Player; 如果您打算在应该只应用于特定类型的同类容器上调用函数,这非常有用 . 功能可以通过以下类似方式实现:

    std::vector<Base*> vector;
    vector.push_back(&square);
    vector.push_back(&triangle);
    for (auto& e : vector)
    {
         auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
         if (!test) // not a triangle
            e.GenericFunction();
         else
            e.TriangleOnlyMagic();
    }
    

    因此,如果只有Triangles具有Rotate函数,如果您尝试在类的所有对象上调用它,则会出现编译器错误 . 使用 dynamic_cast ,您可以模拟 as 关键字 . 要清楚,如果转换失败,则返回无效指针 . 因此 !test 本质上是检查 test 是否为NULL或无效指针的简写,这意味着强制转换失败 .

    自动变量的好处

    在看到动态分配可以做的所有伟大事情之后,你可能想知道为什么没有人不会一直使用动态分配?我已经告诉你一个原因,堆很慢 . 如果你不需要所有的记忆,你不应该滥用它 . 所以这里有一些缺点,没有特别的顺序:

    • 这很容易出错 . 手动内存分配很危险,而且容易发生泄漏 . 如果您不熟练使用调试器或 valgrind (内存泄漏工具),您可能会将头发拉出头部 . 幸运的是,RAII成语和智能指针可以缓解这一点,但你必须熟悉诸如“三规则”和“五规则”等实践 . 这是一个需要掌握的大量信息,而那些不关心的初学者会陷入这个陷阱 .

    • 没有必要 . 与Java和C#不同,在C语言中使用 new 关键字是惯用的,只有在需要时才应该使用它 . 常见的一句话是,如果你有锤子,一切看起来都像钉子 . 而以C开头的初学者害怕指针并学会习惯使用堆栈变量,Java和C#程序员 start 使用指针而不理解它!这实际上是走错了路 . 你必须抛弃你所知道的一切,因为语法是一回事,学习语言是另一回事 .

    1.(N)RVO - Aka,(命名)返回值优化

    许多编译器所做的一个优化是 elisionreturn value optimization . 这些东西可以避免不必要的复制,这对于非常大的对象很有用,例如包含许多元素的向量 . 通常的做法是使用指向 transfer ownership 的指针,而不是将大对象复制到它们周围 . 这导致了 move semanticssmart pointers 的开始 .

    如果您使用指针,(N)RVO会发生 NOT . 如果您担心优化,那么利用(N)RVO而不是返回或传递指针更有利且更不容易出错 . 错误泄漏如果函数的调用者负责动态分配的对象等,则会发生这种情况 . 如果指针像烫手山芋一样被传递,则很难跟踪对象的所有权 . 只需使用堆栈变量,因为它更简单,更好 .

  • 3

    在内存利用率非常高的领域,指针很方便 . 例如,考虑使用minimax算法,其中将使用递归例程生成数千个节点,并且稍后使用它们来评估游戏中的下一个最佳移动,解除分配或重置的能力(如在智能指针中)显着减少了内存消耗 . 而非指针变量继续占用空间,直到它的递归调用返回一个值 .

相关问题