Pointers, when are they used?

Started by
17 comments, last by Emmanuel Deloget 17 years ago
Quote:Original post by Crypter
A string, as being an array of characters

std::string is a string
char[] is an array of characters
Advertisement
People commonly say things like "the *real* purpose of pointers is..." and then cite one of their possible uses. The thing is, there are several things they can be used for, and none is really dominant. But more to the point, in modern C++, we have tools to avoid interacting with them directly. And you *shouldn't* normally interact with them directly, because you get into thinking at a lower level than is necessary.

The natural starting point for discussion is the reference. I mean that in an abstract, non-C++-specific way. The idea is, we want to have some kind of variable that doesn't directly store a value (well, not one that we directly *care about*, anyway), but instead can "stand in" for another variable, i.e. alias it. This way, we could share a value between two different data structures, or let called functions affect local variables, by passing a reference instead. (Note the subtle distinction between passing *a* reference and passing *by* reference; the former implies that we're responsible for creating the reference, whereas the latter is more commonly seen in discussion of how the language is implemented.)

In some languages, objects are actually represented by references to "hidden" data structures that you don't actually access directly. For example, Java treats values of object type (i.e. everything except primitives like 'int') in this way, and Python treats everything this way (even simple numbers). These references get passed around as they are, which can allow called functions to modify the variables of their callers. (In Python, however, many built-in types are fundamentally non-modifiable - "immutable".) In Java, when you pass a primitive value, it gets copied across, so the called function *cannot* modify the caller's variable of a primitive type, even though it could with an object - this is because it's not actually the variable that's modified (since the variable "of an object type" actually holds a reference), but the hidden data. For this reason, it is commonly asserted that Java is a pass-by-value language; it doesn't pass by reference, but it instead *passes references* by value. Python, OTOH, could be implemented either way (at least this far), and you'd never have any way to find out which (and you shouldn't care, anyway - it does what it is supposed to do and that's what matters).



Which brings us to C++. C++ provides variables of reference type. An 'int&' is a "reference-to-int". There are a few things in the above description of references that are unspecified, i.e. could be done in different ways:

- Whether or not there is a "null". In Java, object-type variables can also be set to null, meaning that they don't actually refer to an object, but instead to a magical stand-in "there isn't anything here right now". The variable becomes a placeholder. If you try to use the object while it's null, you'll (except for very trivial uses) get a NullPointerException. Hmm - pointer - implementation details leaking through? :s We'll worry about that later, though. In Python, this concept is absent: an object is always an object and there are no such placeholders. If you think it would be useful to have one, then you need to create a workaround. However, it so happens that creating a workaround is often the best solution *anyway* from a software design perspective, because with a bit of cleverness, you can avoid lots of special-case logic to deal with those placeholders.

For example, in Java you might have a String type variable that is set to null. But there is already a value for a String that does a pretty good job of representing "absence of a string": the empty string, i.e. "". If we set the variable to that, instead, then trying to get its length, for example, won't throw the exception, but instead return 0 - a reasonably-expectable value. In Python, of course, you don't have the option of null, so you just use "". If for some reason you actually need to distinguish "this string is 0 characters long" from "this string doesn't actually exist at all", then you will need to make some kind of wrapper - but you should probably rethink your design on a bigger scale first!

C++ takes the Python approach, and does not provide a null for its references. It does for pointers, which is one reason they are sometimes useful.

- Whether or not the reference can be "rebound". That is, whether we are allowed to pick up a variable of reference type and cause it to start referring to another one.

In both Java and Python, this works. After all, the "variable of reference type" is pretending to hold the actual object, and it would be quite annoying if all our variables were 'const'. ;) In fact, Python gets around a lot of limitations by implicitly rebinding references behind the scenes. For example, objects of 'int' type are actually immutable in Python - the value in the hidden data can't be changed from Python. So when you write 'x += 5', what it actually does is to translate that into 'x = x + 5', perform the addition (creating a brand new 'int' hidden-data structure), and then rebind x to point to the result. (The old hidden-data structure will then get garbage-collected later.)

However, C++ references *do not* do this. There is a subtle distinction: C++ references are pretending to be *other names for* the actual referred-to thing (not always an object, i.e. instance of a class type ;) ), rather than the things themselves. Therefore, when you "assign to" a variable of reference type, you actually assign to the referred-to thing. There is no legal way to cause the reference to re-bind to another thing, and any attempt to hack around this (based on your knowledge of how the compiler is implementing references) invokes undefined behaviour. I.e., even if you think you know perfectly what is going on, it could *still* do anything, because the compiler's optimizer is specifically allowed to assume that you are not doing anything like this, because it's not allowed for you.



As a consequence the above design decisions, C++ references *must be initialized*. The language won't let you get away with an uninitialized reference, because (a) since there is no "null", the reference has to refer to *something*, and at this point the compiler doesn't know what to refer to; and (b) you can't just make the decision later, because that would require binding the uninitialized reference, and binding is simply a special case of rebinding, which is not allowed.

This is a good thing. It forces you to initialize at least some of your variables, and initializing variables is good because they are never in an invalid state (this makes it easier to reason about program correctness). It also forces you to take advantage of the C++ language feature whereby variables don't have to be declared at the top of their scope. That means you can find out what is necessary in order to initialize the reference first, and then go ahead and initialize it.

In fact, those ideas are applicable to variables not of reference type, too, so let's try them out on your original code :)

int main (){	int Number = 100;	int* NumberPtr = &Number


The rest has some other style issues, but we can worry about those some other time. For now, the point is to illustrate how the variables are initialized, and see the benefits:

- The code is shortened, and we avoid redundancy in mentioning the variable names. More to the point, we don't have pairs of statements that have to be kept in sync (declaration and initial-assignment): if we decide to rename one of the variables, there's only one line of code that changes. This is the Don't Repeat Yourself principle on a micro scale.
- There is no point in time at which the variable contents are unknown. Even though there was "obviously" nothing done with the unknown contents in the previous code, it is vastly more obvious now, because the situation simply never occurs.
- In order to make the initialization work, we are forced to highlight the dependancy between NumberPtr and Number by initializing Number first. Without initialization, we could have put the variable declarations in the other order (BTW, declaring variables of different type in the same line like that is seen as pretty sketchy by most C++ folk - it works because the C designers thought it would be convenient, but it tends to lead to confusion) and the reader of the code would have been left wondering about the lack of parallelism.



I'll just sum up about references before moving on to the next major concept. There's only one more thing to point out, really, which is how references are used *practically* in C++. Well, most of the time, they are used as function parameters. They *can* be used as data members of an object, but you have to be very careful that way, because (a) they *must* be initialized in *all* constructors *from the initialization list* (assigning to members inside a constructor body is *still* assignment and not really initialization), and (b) the default copy-constructor and assignment operator won't work, and you'll probably need to think really hard about how these should be handled (often, the correct handling is to disallow those operations completely).

But you probably won't have to worry about such things for a while. Instead, here's a more practical use: creating a local variable of a reference type in order to avoid repeated "lookup" operations within a complex data structure.

In C, we used to do this kind of thing with pointers. Naturally, doing things directly with pointers is tricky, and writing code this way was error-prone - unfortunate, since the goal is to avoid errors by avoiding copy-pasted code (which of course, tends to get messed up when it's maintained, due to one of the copies not being altered properly). It also led to people creating structures that pointed all over the place when it was possible to just use local data, actually making performance worse. (I still see this a lot in C++, unfortunately, because old bad habits are hard to break.) The idea there was to make it easier to get the syntax right by making *everything* a pointer, so you wouldn't have to think about which things were pointers and which weren't. :s

Of course, the reason you can't just create a *value* from the looked-up data member is that you want to change the data member, not simply your local variable. In other words, we want the variable to act as another name for this deeply-nested data member... hmm, "another name"... sounds like a job for a reference to me!

Let's see the technique in action:

void wibble(Thing& foo) {  // ORIGINAL - bad; repeats the lookup procedure, which makes the code harder   // to maintain and longer than it should be  // foo.bar.baz.x += 1;  // foo.bar.baz.y *= 2;  // foo.bar.baz.z %= 3;  // C-STYLE, without changing the data structure - tricky and "not clean",  // i.e. things are not symmetric. Remember, in C we can't pass a "Thing&", so  // we would normally pass a Thing* instead, so we have to dereference that...  // Member* m = &(foo->bar.baz); <-- eww!  // m->x += 1;  // m->y *= 2;  // m->z %= 3;  // C-STYLE, assuming changes to data structure - symmetrical, but we end up  // adding huge amounts of code elsewhere to dynamically allocate and   // deallocate the 'bar' and 'baz' members and make sure everything is  // consistent. Plus we add extra data and spread our struct around all over  // memory. Horrible!  // Member* m = foo->bar->baz;   // where 'baz' is now of type Member* instead of Member, and similarly for  // foo and bar. Also, the function would again take a Thing* parameter.  // m->x += 1;  // m->y *= 2;  // m->z %= 3;  // GOOD C++ STYLE, using a reference. (Of course, better C++ *design* might  // involve giving Member a member function that makes the changes and then  // just calling that ;) )  Member& m = foo.bar.baz;  m.x += 1;  m.y *= 2;  m.z %= 3; // everything is just .'s again, and the original problem is solved.}// When we *call* wibble(), we can use a variable of type Thing, rather than// Thing& - and this is what we normally do (although a Thing& will also work,// exactly as you'd expect). The compiler simply matches up the types, says// "I know how to convert a Thing to a Thing&, and that conversion is legal",// and everything runs smoothly. The net effect is that we can pretend to pass// *by* reference when we really pass *a* reference, by relying on an implicit// conversion.int main() {  Thing t;  wibble(t);  // t.bar.baz.x has now been incremented, etc. The changes done in wibble()  // are "seen" at this point.}


Of course, you're not required to understand the "C-style" techniques at this point in the lesson. After all, the whole point is that we *don't* want to use them ;) But I put them in for comparison.
Quote:
std::string is a string
char[] is an array of characters

std::string encapsulates the array of characters as an ADT.

Of course, in C++, std::string is the better choice for alot
of reasons[smile]
Just a quick note...I had a program once that dealt with a large amount of data. I wasn't using pointers to begin with, and the program actually crashed due to running out of memory. I'm sure there could have been other fixes for what I was doing, but as it was it used a function that called itself, and passed an object as an argument. Without using pointers, the object was copied each time, and since the object contained a LOT of data, the program very quickly used all available memory (and still needed more).

Final State: pointers! instead of passing massive amounts of data, multiple times (nested), there was only a 4-byte value passed each time.


This program was not graphics-heavy, so pointers can be dramatically helpful even in a non-graphics program (depending on what the program does, and how it does it of course...).
Quote:Original post by Zahlman
yea...I'll clip this huge post


VERY VERY nice post. rate++;

Chad.



It would be good idea to make mention of smart pointers. These pointers mange their own instances and garbage collect after they leave scope. Knowing which one to use and when takes a little bit of time but once you get the hang of it, you start to gain that time back by not having to debug as much or write more code than you need to.

I was reading this at work today, highly recommend it to people wanting to know more about smart pointers.
http://www.codeproject.com/vcpp/stl/boostsmartptr.asp

Another good class to have a look at is the boost::array class. It’s a light wrapper on top of an array structure. Arrays are a very common application for pointers.

http://www.boost.org/doc/html/array.html
Useless textbook examples often confuse new programmers about what pointers are.

I think you get the gist of WHAT they are, but you are asking about why you should use them.

Essentially, pointers store a memory address. That's it. They can be used to allocate/deallocate memory and therefore store the address of the allocation.

Since you know this stuff, the first thing I would advise you to read up on would be passing function parameters by reference VS passing function parameters by value (any C++ textbook should suffice). This should give you a little bit more insight into how and (some of the reasons) why we use pointers in C++. Something to keep in mind is that languages such as Java and C# still behave the same way, however they do all the "pointer work" behind the scenes.

As for textbook examples... rarely are they useful in demonstrating why we do anything, but often they are good enough to explain how. Keep on reading and you should pick up on things fast. Just remember that pointers are usually what seperate a lot of would be programmers from the real programmers, and it's usually from a conceptual standpoint. When you understand them, you'll understand other languages that don't use explicit pointers better. Take your time, and know that you're probably already slightly ahead of the game.
trick, what you are describing is a situation in which you should be using references rather than pointers.
My post is a response to this post - not a response to the OP (Zahlman's post is a must read). You can view it as an enhancement to Glak's answer.

Quote:Original post by trick
Just a quick note...I had a program once that dealt with a large amount of data. I wasn't using pointers to begin with, and the program actually crashed due to running out of memory. I'm sure there could have been other fixes for what I was doing, but as it was it used a function that called itself, and passed an object as an argument. Without using pointers, the object was copied each time, and since the object contained a LOT of data, the program very quickly used all available memory (and still needed more).

Final State: pointers! instead of passing massive amounts of data, multiple times (nested), there was only a 4-byte value passed each time.

This program was not graphics-heavy, so pointers can be dramatically helpful even in a non-graphics program (depending on what the program does, and how it does it of course...).


It's more likely because you exhausted the stack (stack overflow exception).

When you call a function, the parameters of this functions are (most of the time) stored on the stack.

The stack is also used to "allocate" automatic storage duration variables (variables that will destroy themselves when you go out of scope) and a bunch of other information (return address of the caller, and so on).

  • If you use a function signature such as:
    void function(Class obj1, Class obj2);

    Then you will create copies of obj1 and obj2 on the stack. If Class is farily big, you'll run in trouble very quickly.


  • If your function declaration looks like:
    void function(Class *pobj1, Class *pobj2)

    Then you will store copies of pobj1 and pobj2 on the stack as well. But in this cases, these two variables are pointers, and don't take much memory. So it appear to be a good solution to the problem above, but both pobj1 and pobj2 can now be NULL (so you must check that in your called code) and you have the possibility to modify the underlying objects). But it's not the best solution.


  • If your function declaration looks like
    void function(Class& obj1, Class& obj2)

    You will create copies of references to obj1 and obj2 on the stack. These references are (most of the time) implemented as pointer (internally; but you don't have to deal with such kind of detail), and as a consequence it takes the same stack space than the solution above. The good thing is that references are not supposed to be NULL - so you can remove the checks that you had to add in the pointer version. The bad thing is that you can still modify both obj1 and obj2 inadvertedly.


  • So our next solution is to consider a correct use of the const keyword:
    void function(const Class& obj1, const Class& obj2)
    Got it! These are references, so they don't take much stack space, and const references to object makes these objects immutable (unless you use the mutable keyword internally, but that's kind of rare). From a caller point of view, this is very similar to the first (bad) solution - it behaves in the same way (minus some very tiny details).

What I want to say is that instead of using pointers in this case, you should have used const references. They are safer, less error prone, and far easier to deal with.

Of coursse, you can also play with constant pointer to constant objects. The syntax would be
void function(const Class const* obj1, const Class const* obj2)

I'm pretty sure you prefer the const reference version [smile].

The rule of thumb is: whenever you want oto use a pointer, try to see if a reference is not a better solution.

Regards,

This topic is closed to new replies.

Advertisement