Why not use pointers for everything in C++?

2020-01-24 19:11发布

Suppose that I define some class:

class Pixel {
    public:
      Pixel(){ x=0; y=0;};
      int x;
      int y;
}

Then write some code using it. Why would I do the following?

Pixel p;
p.x = 2;
p.y = 5;

Coming from a Java world I always write:

Pixel* p = new Pixel();
p->x = 2;
p->y = 5;

They basically do the same thing, right? One is on the stack while the other is on the heap, so I'll have to delete it later on. Is there any fundamental difference between the two? Why should I prefer one over the other?

23条回答
相关推荐>>
2楼-- · 2020-01-24 19:16

The code:

Pixel p;
p.x = 2;
p.y = 5;

does no dynamic allocation of memory - there is no searching for free memory, no updating of memory usage, nothing. It is totally free. The compiler reserves space on the stack for the variable at compile time - it works out have much space to reserve and creates a single opcode to move the stack pointer the required amount.

Using new requires all that memory management overhead.

The question then becomes - do you want to use stack space or heap space for your data. Stack (or local) variables like 'p' require no dereferencing whereas using new adds a layer of indirection.

查看更多
混吃等死
3楼-- · 2020-01-24 19:16

I'd say it's a lot about a matter of taste. If you create an interface allowing methods to take pointers instead of references, you are allowing the caller to pass in nil. Since you allow the user to pass in nil, the user will pass in nil.

Since you have to ask yourself "What happens if this parameter is nil?", you have to code more defensively, taking care of null checks all the time. This speaks for using references.

However, sometimes you really want to be able to pass in nil and then references are out of the question :) Pointers give you greater flexibility and allow you to be more lazy, which is really good. Never allocate until know you have to allocate!

查看更多
贪生不怕死
4楼-- · 2020-01-24 19:18

They are not the same until you add the delete.
Your example is overly trivial, but the destructor may actually contain code that does some real work. This is referred to as RAII.

So add the delete. Make sure it happens even when exceptions are propagating.

Pixel* p = NULL; // Must do this. Otherwise new may throw and then
                 // you would be attempting to delete an invalid pointer.
try
{
    p = new Pixel(); 
    p->x = 2;
    p->y = 5;

    // Do Work
    delete p;
}
catch(...)
{
    delete p;
    throw;
}

If you had picked something more interesting like a file (which is a resource that needs to be closed). Then do it correctly in Java with pointers you need to do this.

File file;
try
{
    file = new File("Plop");
    // Do work with file.
}
finally
{
    try
    {
        file.close();     // Make sure the file handle is closed.
                          // Oherwise the resource will be leaked until
                          // eventual Garbage collection.
    }
    catch(Exception e) {};// Need the extra try catch to catch and discard
                          // Irrelevant exceptions. 

    // Note it is bad practice to allow exceptions to escape a finally block.
    // If they do and there is already an exception propagating you loose the
    // the original exception, which probably has more relevant information
    // about the problem.
}

The same code in C++

std::fstream  file("Plop");
// Do work with file.

// Destructor automatically closes file and discards irrelevant exceptions.

Though people mention the speed (because of finding/allocating memory on the heap). Personally this is not a deciding factor for me (the allocators are very quick and have been optimized for C++ usage of small objects that are constantly created/destroyed).

The main reason for me is object life time. A locally defined object has a very specific and well defined lifetime and the the destructor is guaranteed to be called at the end (and thus can have specific side effects). A pointer on the other hand controls a resource with a dynamic life span.

The main difference between C++ and Java is:

The concept of who owns the pointer. It is the responsibility of the owner to delete the object at the appropriate time. This is why you very rarely see raw pointers like that in real programs (as there is no ownership information associated with a raw pointer). Instead pointers are usually wrapped in smart pointers. The smart pointer defines the semantics of who owns the memory and thus who is responsible for cleaning it up.

Examples are:

 std::auto_ptr<Pixel>   p(new Pixel);
 // An auto_ptr has move semantics.
 // When you pass an auto_ptr to a method you are saying here take this. You own it.
 // Delete it when you are finished. If the receiver takes ownership it usually saves
 // it in another auto_ptr and the destructor does the actual dirty work of the delete.
 // If the receiver does not take ownership it is usually deleted.

 std::tr1::shared_ptr<Pixel> p(new Pixel); // aka boost::shared_ptr
 // A shared ptr has shared ownership.
 // This means it can have multiple owners each using the object simultaneously.
 // As each owner finished with it the shared_ptr decrements the ref count and 
 // when it reaches zero the objects is destroyed.

 boost::scoped_ptr<Pixel>  p(new Pixel);
 // Makes it act like a normal stack variable.
 // Ownership is not transferable.

There are others.

查看更多
等我变得足够好
5楼-- · 2020-01-24 19:18

Use pointers and dynamically allocated objects ONLY WHEN YOU MUST. Use statically allocated (global or stack) objects wherever possible.

  • Static objects are faster (no new/delete, no indirection to access them)
  • No object lifetime to worry about
  • Fewer keystrokes More readable
  • Much more robust. Every "->" is a potential access to NIL or invalid memory

To clarify, by 'static' in this context, I mean non-dynamically allocated. IOW, anything NOT on the heap. Yes, they can have object lifetime issues too - in terms of singleton destruction order - but sticking them on the heap doesn't usually solve anything.

查看更多
▲ chillily
6楼-- · 2020-01-24 19:21

Objects created on the stack are created faster than objects allocated.

Why?

Because allocating memory (with default memory manager) takes some time (to find some empty block or even allocate that block).

Also you don't have memory management problems as the stack object automatically destroys itself when out of scope.

The code is simpler when you don't use pointers. If your design allows you to use stack objects, I recommend that you do it.

I myself wouldn't complicate the problem using smart pointers.

OTOH I have worked a little in the embedded field and creating objects on the stack is not very smart (as the stack allocated for each task/thread is not very big - you must be careful).

So it's a matter of choice and restrictions, there is no response to fit them all.

And, as always don't forget to keep it simple, as much as possible.

查看更多
Melony?
7楼-- · 2020-01-24 19:22

First case is best unless more members are added to Pixel class. As more and more member gets added, there is a possibility of stack overflow exception

查看更多
登录 后发表回答