Pointers/References

Started by
48 comments, last by WillC 19 years, 3 months ago
_the_phantom_: You're right, that is a better example - if I'm calling that like this:

copyStructToStruct(someMyStruct, someOtherMyStruct);


then personally I'm immediately wondering which is source and which is destination. With the extra ampersand of the pointer version, it's pretty obvious which is which.

Harry.
Advertisement
Quote:Original post by HarryW
It's not really about people not knowing the function prototype while writing the code. It's about reading (quickly) code that uses many different functions, when you don't know (or have forgotten) what the prototypes are.

In that case I'd read the prototype. Point is, why am I reading the code? Maintainence/Extension? Then I should make sure I understand what's going on before adding or changing anything. If I don't know what a function does, I'll check its prototype.

Quote:Also, Coder gave this quote:
Quote:...

I'd hardly call myself an 'old line' C programmer, but I'm intrigued by this statement. I've read it a few times now and it's looking a lot like some buzzwords strung together to make a catchy quote. What's the information here that's being hidden, and why is it beneficial to hide it?

1. These aren't my words, it's copied as is from C++ FAQ Lite. And the old-line part wasn't the emphasis [smile]

2. The information that's being hidden is the mechanism by which the implementation works - the fact that it's actually passing an address in machine code and then dereferences it on the other side.

3. It is beneficial to hide it because when I think about solving a problem, I say things like
"I'm going to read this into an integer, and then do this and that", and not "I'm going to read this into the memory pointed to by this address, and then do this and that". Code with references is closer to pseudo-code, the way I design algorihtms. I read it faster, I grok it faster, it looks cleaner.

Quote:The point that I'm trying to make with this example is that, for someone looking at the DoTest() function for the first time, with the call using the reference it's ambiguous whether or not it may change the second parameter, since I need to look at the function definition to know if it's a const parameter or not. In the pointer case I can see at a glance that the structure might be changed without having to look at the function definition.

This kind of thing is very important when working in a big team, were ambiguity can lead to bugs.

In the example you've given, the functions don't have meaningful names, and neither do the variables. The way I see it:
- If function and variable names are descriptive, things are evident from context, with references or pointers. But you have to KNOW the functions.
If you don't know the functions, you'll have to look up their prototypes. If you look up their prototypes, it doesn't make a difference whether you're using pointers or references.

With code like this:
size_t num_selected_files;m_folder_view->get_num_selected_files(num_selected_files, _T("*.bmp"));// orstd::vector<string_t> selected_files;m_folder_view->get_selected_files(selected_files);...etc


I understand where you're coming from - I've been there. And you may be right. Personally, I used to think using pointers was great self-documentation. I read the reasoning behind using references, and thought of giving it a go. Never looked back [smile]

Quote:Original post by HarryW
_the_phantom_: You're right, that is a better example - if I'm calling that like this:

copyStructToStruct(someMyStruct, someOtherMyStruct);


then personally I'm immediately wondering which is source and which is destination. With the extra ampersand of the pointer version, it's pretty obvious which is which.

- On a sidenote, I'd use the copy constructor or assignment operator for this one [smile]
- That's a convention. In similar situations, I'd say destination is first, source is second (as it is in Intel asm and standard C memory functions), and then use references. That would be:
- Just as clear as your convention (to me and my team and almost every other programmer who've used C string/mem functions).
- Cleaner-looking, because of references.

In fact, if I see code like:
copyStructToStruct(&someMyStruct, someOtherMyStruct);

The fact that you're using a pointer for the first parameter would make me think the second one is passed by value, for some reason. Again, because I'm not used to such convention. And I'd have to lookup the function prototype. If I'm used to your convention, on the other hand, I'll quickly work it out, of course.

Quote:Original post by Coder
- That's a convention. In similar situations, I'd say destination is first, source is second (as it is in Intel asm and standard C memory functions), and then use references. That would be:
- Just as clear as your convention (to me and my team and almost every other programmer who've used C string/mem functions).

Yes, that would be just as self-explanatory, but it's very context-specific. Not all functions of the kind we're talking about (with non-const parameters passed by reference) have a source and destination.

Quote:In that case I'd read the prototype. Point is, why am I reading the code? Maintainence/Extension? Then I should make sure I understand what's going on before adding or changing anything. If I don't know what a function does, I'll check its prototype.
Generally I'd agree, but consider this situation: there's 2 weeks left til the deadline to go gold. QA has been working double shifts and I've got a big list of bugs assigned to me. If I don't get at least half of them fixed by this evening, there's a dev manager that's gonna be on my back about it. Maybe I wasn't even working on this game until a month ago, but as usual the crunch has hit and the producers are throwing warm bodies at the problem, but if that's the case then it's probably not using my coding standard. In this case, I want it to be as obvious as possible what's happening so I can roughly find the likely source of the bug by scanning through code, and the less headers I need to examine the better. I'd like to be able to make reasonable assumptions about the code at first, and then verify those assumptions later if it seems they're likely to be inaccurate.

Harry.
Quote:Original post by _the_phantom_
the problem with your 'example' is that the function name is bad to start with.
If it was changed to something more meaning full (such as CopyStructToStruct(const &, &) ) then using references makes perfect sense, infact more so.

*** Source Snippet Removed ***

Now, I'd argue that a function with a sane name with references is just as a good, if not better, than the pointer system.
However, if you want to use pointers with bad function names which are non-descriptive in themselves thats up to you [smile]


Yes, I totally agree that using descriptive names is a good idea, and I'd rather hoped that it would be obvious that naming functions 'SomeFunctionUsingRef' is bad; my example was meant to be an abstract illustration, not intended to be taken literally.

However, experience and many years of mistakes as taught me the hard way that you need to use every weapon available to make sure that code is a clear as possible in a team situation.

* Some people will look at the function definition to see exactly what it does before they try and use it; Some people won't so...
* Give the function as descriptive a name as possible, and make sure it's well commented. Now unfortunetly, functions get changed, and their descriptive names don't get updated, or their comments become out of date. Yes I know this bad, but it happens...
* So as a final layer of defence, keep to a standard that makes it clearer what will happen, even if the name is no help.

The problem with just using a reference and relying on a descriptive name is that it leaves a gap in your defences, and with lots of people in a team, someone will find a way to break though. At least by adding the extra layer of protection of un-ambiguity, you are stopping a few (nasty) bugs from happening.

In a perfect 'ideal' world I would totally agree that references are better; but I'm taking about the practical game developer world of deadlines, last minute changes, big teams with new junior programmers, PS2 dev kits with rubbish debuggers etc etc. It's a harsh dirty world, and you need all the protection you can get to survive.

Quote:Original post by HarryW
Generally I'd agree, but consider this situation: there's 2 weeks left til the deadline to go gold. QA has been working double shifts and I've got a big list of bugs assigned to me. If I don't get at least half of them fixed by this evening, there's a dev manager that's gonna be on my back about it. Maybe I wasn't even working on this game until a month ago, but as usual the crunch has hit and the producers are throwing warm bodies at the problem, but if that's the case then it's probably not using my coding standard. In this case, I want it to be as obvious as possible what's happening so I can roughly find the likely source of the bug by scanning through code, and the less headers I need to examine the better. I'd like to be able to make reasonable assumptions about the code at first, and then verify those assumptions later if it seems they're likely to be inaccurate.


Exactly.
You should never use a function for which you don't know exactly what the parameters should be. Always look it up, and maybe you wont be running so close to the deadline since there would be fewer bugs.
e.g. It's all too easy for new programmers to get the last two arguments to memset the wrong way around. That doesn't play too well with the execution of your program!

References make for cleaner code, and prevent many sources of bugs, such as writing x++, when you meant to write *x++ to increment the pointed-to value, since x was a pointer. Not a mistake you can make with a reference!
They are not just eye-candy, they are a useful tool for preventing bugs both when first written, and when modified later, and only fools ignore easy ways to prevent bugs.

No function can be held responsible for crashing due to accessing NULL or invalid references, for the only way to create a NULL reference is for the caller to dereference NULL itself, which is of course invalid.
However, NULL is always a valid thing to pass for a pointer parameter and therefore really should be handled, at the very least any intended precondition of non-null pointers should be checked in debug builds. With references it's implicit that NULL is not a valid parameter so there's less work to do.

Use them or do not - Whatever! Ignore them, and it's only a matter of time before you find a bug that using references could have avoided. Just don't pretend that they aren't actually a better choice in many circumstances.

[Edited by - iMalc on January 17, 2005 1:07:22 AM]
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote:Original post by iMalc
You should never use a function for which you don't know exactly what the parameters should be. Always look it up, and maybe you wont be running so close to the deadline since there would be fewer bugs.
e.g. It's all too easy for new programmers to get the last two arguments to memset the wrong way around. That doesn't play too well with the execution of your program!

That's fine, but this isn't about writing code that uses the functions, it's about reading code where they're already used. Besides, the whole avoiding-the-crunch issue is not particularly a programming problem, it's a political one. If you know it's going to happen, I'd suggest it's better to plan for it.

Harry.
Quote:Original post by HarryW
That's fine, but this isn't about writing code that uses the functions, it's about reading code where they're already used.


Writing code is a prerequisite to reading it.

Also, again, there's something wrong with your naming if it isn't evident what's going to be changed and what isn't. Don't apply conventions on top of the compiler's language in order to communicate intent, when you have the alternative of communicating, well, in English.

Quote:Besides, the whole avoiding-the-crunch issue is not particularly a programming problem, it's a political one. If you know it's going to happen, I'd suggest it's better to plan for it.


...

Agreed.
This is one of those things that it's impossible for everyone to agree on. Everyone has their own different coding styles and conventions, and it's impossible to say that one way is right and one way is wrong.

What this boils down to is how these conventions eventually seep into our subconscious and become automatic. At this point, it becomes very difficult to read other peoples code that use a different style because our brains process the code at a glance before we have a chance to think about it properly. (Also related to this is when you try to use someone else’s machine that has different key shortcuts in .NET than you're used to).

For example, (and going off on a tangent for a moment), some people bracket code like this...

    if (blah) {	blah    }


and some like this...

    if (blah)     {        blah    }


I don't think that either way is right or wrong, but I use the second style, and my brain is programmed to recognise that pattern of vertically aligned braces. When I see non-aligned brackets like the first example I have to stop and think about it consciously for a moment, because it's not what my brain is expecting, whereas with the second example my brain immediately knows where the code block starts and ends. Obviously this may seem silly in such a simple example, but in real world code it becomes important.

The reason why I prefer the second bracing method is because the 'microcode' that my brain has to learn is the simplest....

‘If I see an open bracket then a code block starts on the next line. Scan vertically downward until you find the next closing bracket to find the end of the code block.’

For the non-aligned bracket method the rules are more complex, and so take longer to process.

So coming back on topic again, the same principle applies for function parameter conventions. When I look at this line of code….

CopyStructToStructPtr(constStruct, &nonConstStruct);

The first thing my brain sees is…

‘Long name, horizontal open and close brackets’ – Looks like a function call
‘& on one of the paramers’ - Param may be modified (big red flashing warning ‘be careful’ light in brain)

Now my conscious brain kicks in and reads the function name, which re-enforces the information my sub-conscious brain has given me; which is great.


Now when I look at this line of code,

CopyStructToStruct(constStruct, nonConstStruct);

all I see instantaneously is that it’s a function. I have to wait for my conscious brain to read the function name before I get a clue that one of the parameters may be modified (assuming the name is descriptive enough).

The fact is that the brain is much better and faster at processing simple abstract patterns and shapes like this than it is at reading a function name and working out its meaning. Of course we’re only talking about fractions of a second here, but that makes all the difference when you’re quickly scanning through unfamiliar code. It allows you to quickly identify areas of code that require further conscious inspection, and skip those that don’t.

Of course the most important thing is not what actual conventions are used, but rather that everyone on a team uses the Same conventions, so that they can all obtain (eventually) the same sub-conscious processing ability when viewing the code.

[Edited by - WillC on January 17, 2005 8:41:47 AM]

This topic is closed to new replies.

Advertisement