c++ - Why should I use a pointer rather than the object itself?

ID : 568

viewed : 214

Tags : c++c++11pointersc++-faqc++





Top 5 Answer for c++ - Why should I use a pointer rather than the object itself?

vote vote

100

It's very unfortunate that you see dynamic allocation so often. That just shows how many bad C++ programmers there are.

In a sense, you have two questions bundled up into one. The first is when should we use dynamic allocation (using new)? The second is when should we use pointers?

The important take-home message is that you should always use the appropriate tool for the job. In almost all situations, there is something more appropriate and safer than performing manual dynamic allocation and/or using raw pointers.

Dynamic allocation

In your question, you've demonstrated two ways of creating an object. The main difference is the storage duration of the object. When doing Object myObject; within a block, the object is created with automatic storage duration, which means it will be destroyed automatically when it goes out of scope. When you do new Object(), the object has dynamic storage duration, which means it stays alive until you explicitly delete it. You should only use dynamic storage duration when you need it. That is, you should always prefer creating objects with automatic storage duration when you can.

The main two situations in which you might require dynamic allocation:

  1. You need the object to outlive the current scope - that specific object at that specific memory location, not a copy of it. If you're okay with copying/moving the object (most of the time you should be), you should prefer an automatic object.
  2. You need to allocate a lot of memory, which may easily fill up the stack. It would be nice if we didn't have to concern ourselves with this (most of the time you shouldn't have to), as it's really outside the purview of C++, but unfortunately, we have to deal with the reality of the systems we're developing for.

When you do absolutely require dynamic allocation, you should encapsulate it in a smart pointer or some other type that performs RAII (like the standard containers). Smart pointers provide ownership semantics of dynamically allocated objects. Take a look at std::unique_ptr and std::shared_ptr, for example. If you use them appropriately, you can almost entirely avoid performing your own memory management (see the Rule of Zero).

Pointers

However, there are other more general uses for raw pointers beyond dynamic allocation, but most have alternatives that you should prefer. As before, always prefer the alternatives unless you really need pointers.

  1. You need reference semantics. Sometimes you want to pass an object using a pointer (regardless of how it was allocated) because you want the function to which you're passing it to have access that that specific object (not a copy of it). However, in most situations, you should prefer reference types to pointers, because this is specifically what they're designed for. Note this is not necessarily about extending the lifetime of the object beyond the current scope, as in situation 1 above. As before, if you're okay with passing a copy of the object, you don't need reference semantics.

  2. You need polymorphism. You can only call functions polymorphically (that is, according to the dynamic type of an object) through a pointer or reference to the object. If that's the behavior you need, then you need to use pointers or references. Again, references should be preferred.

  3. You want to represent that an object is optional by allowing a nullptr to be passed when the object is being omitted. If it's an argument, you should prefer to use default arguments or function overloads. Otherwise, you should preferably use a type that encapsulates this behavior, such as std::optional (introduced in C++17 - with earlier C++ standards, use boost::optional).

  4. You want to decouple compilation units to improve compilation time. The useful property of a pointer is that you only require a forward declaration of the pointed-to type (to actually use the object, you'll need a definition). This allows you to decouple parts of your compilation process, which may significantly improve compilation time. See the Pimpl idiom.

  5. You need to interface with a C library or a C-style library. At this point, you're forced to use raw pointers. The best thing you can do is make sure you only let your raw pointers loose at the last possible moment. You can get a raw pointer from a smart pointer, for example, by using its get member function. If a library performs some allocation for you which it expects you to deallocate via a handle, you can often wrap the handle up in a smart pointer with a custom deleter that will deallocate the object appropriately.

vote vote

82

There are many use cases for pointers.

Polymorphic behavior. For polymorphic types, pointers (or references) are used to avoid slicing:

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. For non-polymorphic types, a pointer (or a reference) will avoid copying a potentially expensive object

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 

Note that C++11 has move semantics that can avoid many copies of expensive objects into function argument and as return values. But using a pointer will definitely avoid those and will allow multiple pointers on the same object (whereas an object can only be moved from once).

Resource acquisition. Creating a pointer to a resource using the new operator is an anti-pattern in modern C++. Use a special resource class (one of the Standard containers) or a smart pointer (std::unique_ptr<> or std::shared_ptr<>). Consider:

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

vs.

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

A raw pointer should only be used as a "view" and not in any way involved in ownership, be it through direct creation or implicitly through return values. See also this Q&A from the C++ FAQ.

More fine-grained life-time control Every time a shared pointer is being copied (e.g. as a function argument) the resource it points to is being kept alive. Regular objects (not created by new, either directly by you or inside a resource class) are destroyed when going out of scope.

vote vote

76

There are many excellent answers to this question, including the important use cases of forward declarations, polymorphism etc. but I feel a part of the "soul" of your question is not answered - namely what the different syntaxes mean across Java and C++.

Let's examine the situation comparing the two languages:

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 

The closest equivalent to this, is:

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. 

Let's see the alternative C++ way:

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... 

The best way to think of it is that -- more or less -- Java (implicitly) handles pointers to objects, while C++ may handle either pointers to objects, or the objects themselves. There are exceptions to this -- for example, if you declare Java "primitive" types, they are actual values that are copied, and not pointers. So,

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. 

That said, using pointers is NOT necessarily either the correct or the wrong way to handle things; however other answers have covered that satisfactorily. The general idea though is that in C++ you have much more control on the lifetime of the objects, and on where they will live.

Take home point -- the Object * object = new Object() construct is actually what is closest to typical Java (or C# for that matter) semantics.

vote vote

70

Preface

Java is nothing like C++, contrary to hype. The Java hype machine would like you to believe that because Java has C++ like syntax, that the languages are similar. Nothing can be further from the truth. This misinformation is part of the reason why Java programmers go to C++ and use Java-like syntax without understanding the implications of their code.

Onwards we go

But I can't figure out why should we do it this way. I would assume it has to do with efficiency and speed since we get direct access to the memory address. Am I right?

To the contrary, actually. The heap is much slower than the stack, because the stack is very simple compared to the heap. Automatic storage variables (aka stack variables) have their destructors called once they go out of scope. For example:

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

On the other hand, if you use a pointer dynamically allocated, its destructor must be called manually. delete calls this destructor for you.

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

This has nothing to do with the new syntax prevalent in C# and Java. They are used for completely different purposes.

Benefits of dynamic allocation

1. You don't have to know the size of the array in advance

One of the first problems many C++ programmers run into is that when they are accepting arbitrary input from users, you can only allocate a fixed size for a stack variable. You cannot change the size of arrays either. For example:

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

Of course, if you used an std::string instead, std::string internally resizes itself so that shouldn't be a problem. But essentially the solution to this problem is dynamic allocation. You can allocate dynamic memory based on the input of the user, for example:

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

Side note: One mistake many beginners make is the usage of variable length arrays. This is a GNU extension and also one in Clang because they mirror many of GCC's extensions. So the following int arr[n] should not be relied on.

Because the heap is much bigger than the stack, one can arbitrarily allocate/reallocate as much memory as he/she needs, whereas the stack has a limitation.

2. Arrays are not pointers

How is this a benefit you ask? The answer will become clear once you understand the confusion/myth behind arrays and pointers. It is commonly assumed that they are the same, but they are not. This myth comes from the fact that pointers can be subscripted just like arrays and because of arrays decay to pointers at the top level in a function declaration. However, once an array decays to a pointer, the pointer loses its sizeof information. So sizeof(pointer) will give the size of the pointer in bytes, which is usually 8 bytes on a 64-bit system.

You cannot assign to arrays, only initialize them. For example:

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 

On the other hand, you can do whatever you want with pointers. Unfortunately, because the distinction between pointers and arrays are hand-waved in Java and C#, beginners don't understand the difference.

3. Polymorphism

Java and C# have facilities that allow you to treat objects as another, for example using the as keyword. So if somebody wanted to treat an Entity object as a Player object, one could do Player player = Entity as Player; This is very useful if you intend to call functions on a homogeneous container that should only apply to a specific type. The functionality can be achieved in a similar fashion below:

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(); } 

So say if only Triangles had a Rotate function, it would be a compiler error if you tried to call it on all objects of the class. Using dynamic_cast, you can simulate the as keyword. To be clear, if a cast fails, it returns an invalid pointer. So !test is essentially a shorthand for checking if test is NULL or an invalid pointer, which means the cast failed.

Benefits of automatic variables

After seeing all the great things dynamic allocation can do, you're probably wondering why wouldn't anyone NOT use dynamic allocation all the time? I already told you one reason, the heap is slow. And if you don't need all that memory, you shouldn't abuse it. So here are some disadvantages in no particular order:

  • It is error-prone. Manual memory allocation is dangerous and you are prone to leaks. If you are not proficient at using the debugger or valgrind (a memory leak tool), you may pull your hair out of your head. Luckily RAII idioms and smart pointers alleviate this a bit, but you must be familiar with practices such as The Rule Of Three and The Rule Of Five. It is a lot of information to take in, and beginners who either don't know or don't care will fall into this trap.

  • It is not necessary. Unlike Java and C# where it is idiomatic to use the new keyword everywhere, in C++, you should only use it if you need to. The common phrase goes, everything looks like a nail if you have a hammer. Whereas beginners who start with C++ are scared of pointers and learn to use stack variables by habit, Java and C# programmers start by using pointers without understanding it! That is literally stepping off on the wrong foot. You must abandon everything you know because the syntax is one thing, learning the language is another.

1. (N)RVO - Aka, (Named) Return Value Optimization

One optimization many compilers make are things called elision and return value optimization. These things can obviate unnecessary copys which is useful for objects that are very large, such as a vector containing many elements. Normally the common practice is to use pointers to transfer ownership rather than copying the large objects to move them around. This has lead to the inception of move semantics and smart pointers.

If you are using pointers, (N)RVO does NOT occur. It is more beneficial and less error-prone to take advantage of (N)RVO rather than returning or passing pointers if you are worried about optimization. Error leaks can happen if the caller of a function is responsible for deleteing a dynamically allocated object and such. It can be difficult to track the ownership of an object if pointers are being passed around like a hot potato. Just use stack variables because it is simpler and better.

vote vote

51

Another good reason to use pointers would be for forward declarations. In a large enough project they can really speed up compile time.

Top 3 video Explaining c++ - Why should I use a pointer rather than the object itself?







Related QUESTION?