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

后端 未结 22 1683
予麋鹿
予麋鹿 2020-11-21 23:26

I\'m coming from a Java background and have started working with objects in C++. But one thing that occurred to me is that people often use pointers to objects rather than t

相关标签:
22条回答
  • 2020-11-22 00:00

    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.

    0 讨论(0)
  • 2020-11-22 00:02

    C++ gives you three ways to pass an object: by pointer, by reference, and by value. Java limits you with the latter one (the only exception is primitive types like int, boolean etc). If you want to use C++ not just like a weird toy, then you'd better get to know the difference between these three ways.

    Java pretends that there is no such problem as 'who and when should destroy this?'. The answer is: The Garbage Collector, Great and Awful. Nevertheless, it can't provide 100% protection against memory leaks (yes, java can leak memory). Actually, GC gives you a false sense of safety. The bigger your SUV, the longer your way to the evacuator.

    C++ leaves you face-to-face with object's lifecycle management. Well, there are means to deal with that (smart pointers family, QObject in Qt and so on), but none of them can be used in 'fire and forget' manner like GC: you should always keep in mind memory handling. Not only should you care about destroying an object, you also have to avoid destroying the same object more than once.

    Not scared yet? Ok: cyclic references - handle them yourself, human. And remember: kill each object precisely once, we C++ runtimes don't like those who mess with corpses, leave dead ones alone.

    So, back to your question.

    When you pass your object around by value, not by pointer or by reference, you copy the object (the whole object, whether it's a couple of bytes or a huge database dump - you're smart enough to care to avoid latter, aren't you?) every time you do '='. And to access the object's members, you use '.' (dot).

    When you pass your object by pointer, you copy just a few bytes (4 on 32-bit systems, 8 on 64-bit ones), namely - the address of this object. And to show this to everyone, you use this fancy '->' operator when you access the members. Or you can use the combination of '*' and '.'.

    When you use references, then you get the pointer that pretends to be a value. It's a pointer, but you access the members through '.'.

    And, to blow your mind one more time: when you declare several variables separated by commas, then (watch the hands):

    • Type is given to everyone
    • Value/pointer/reference modifier is individual

    Example:

    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
    
    0 讨论(0)
  • 2020-11-22 00:02

    A pointer directly references the memory location of an object. Java has nothing like this. Java has references that reference the location of object through hash tables. You cannot do anything like pointer arithmetic in Java with these references.

    To answer your question, it's just your preference. I prefer using the Java-like syntax.

    0 讨论(0)
  • 2020-11-22 00:03

    Let's say that you have class A that contain class B When you want to call some function of class B outside class A you will simply obtain a pointer to this class and you can do whatever you want and it will also change context of class B in your class A

    But be careful with dynamic object

    0 讨论(0)
  • 2020-11-22 00:05
    Object *myObject = new Object;
    

    Doing this will create a reference to an Object (on the heap) which has to be deleted explicitly to avoid memory leak.

    Object myObject;
    

    Doing this will create an object(myObject) of the automatic type (on the stack) that will be automatically deleted when the object(myObject) goes out of scope.

    0 讨论(0)
  • 2020-11-22 00:10

    There are many benefits of using pointers to object -

    1. Efficiency (as you already pointed out). Passing objects to functions mean creating new copies of object.
    2. Working with objects from third party libraries. If your object belongs to a third party code and the authors intend the usage of their objects through pointers only (no copy constructors etc) the only way you can pass around this object is using pointers. Passing by value may cause issues. (Deep copy / shallow copy issues).
    3. if the object owns a resource and you want that the ownership should not be sahred with other objects.
    0 讨论(0)
提交回复
热议问题