Jump to content
  • Advertisement
Sign in to follow this  
irreversible

C++: programmer-chosen argument formatting conventions: pros, cons and implications

This topic is 2520 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

This is a kind of a discussion that I've honestly looked into cannot see what the practical benefit of one over the other might be in a generic situation.

First off, I've been coding for something like 15 years. I don't have any hardcore industry experience, I don't have much experience working with others' codebases and I don't see myself as a professional programmer. I'm pretty experienced, but I'm nevertheless a hobbyist. So don't judge me there.

However, among a few things that I've never completely wrapped my head around are the concepts of constness and function argument formatting. Allow me to start by introducing my coding style, which in most cases is geared towards clarity and minimal clutter.

#define IN
#define OUT
//this is a hypothetical function that serves no practical purpose
void BuildMatrix(IN IModel * model, IN IMatrix4x4 matSrc, OUT IMatrix4x4& matDst);

I would go about calling this function as:

IModel model;
IMatrix4x4 matSrc;
IMatrix4x4 matDst;

BuildMatrix(&model, matSrc, matDst);

Needless to say, this is simple and it works. I doesn't incorporate any constness or unnecessary referencing and hence in my view has fewer things to type and has visually less clutter.

As an alternative, someone else (whom I expect to either have a different coding style, more experience or different preferences) might write something like this and call it accordingly:


void BuildMatrix(const IModel& skeletons, const IMatrix4x4& matSrc, IMatrix4x4& matDst);

IModel model;
IMatrix4x4 matSrc;
IMatrix4x4 matDst;
BuildMatrix(model, matSrc, matDst);


Two questions:

- what practical purpose is there for constness other than telling the compiler and a potential programmer that the first two arguments are not going to be modified? I mean, I could define IN to expand to const and the code would be the same. However I don't think I've ever found myself accidentally modifying an argument that I'm not supposed to (a "const argument") - it's just not logical to do so in the first place if you structure your code properly. The closest situation I can think of is an environment where several developers update a single function and one, for whatever reason, decides to update an argument that is or is supposed to be const (which is kind of a silly mistake to begin with).

- what's with passing everything by reference? So many times I've found myself going over code that passes everything by reference and I've never seen an actual benefit to it. Unless you have an object on the heap (which you do more and more seldom as your code grows) or an already dereferenced object (which would have required a validity check at some point anyway), then you've gained nothing compared to having to reference an object that's on the heap (which costs nothing) and conversely not having to dereference an object on the stack (which doesn't mean you don't need to check the pointer for validity). In both cases, at the end of the day, it boils down to zero loss or zero gain either way. To illustrate, to me the following are functionally equivalent. However, the first one is shorter and more clear:

void BuildMatrix(IN IModel * m) { assert(m != 0); mat = *m; }
void PassThru(IModel * m) { BuildMatrix(m); }

and


void BuildMatrix(const IModel& m) { mat = m; }
void PassThru(IModel * m) { assert(m != 0); BuildMatrix(*m); }

At the end of the day both work and neither is faster. What are your personal preferences and, more importantly, why?

Share this post


Link to post
Share on other sites
Advertisement

The closest situation I can think of is an environment where several developers update a single function and one, for whatever reason, decides to update an argument that is or is supposed to be const (which is kind of a silly mistake to begin with).


Lots of silly mistakes occur with multiple developers of different skill levels.

Unless you have an object on the heap (which you do more and more seldom as your code grows)


Then you're doing something wrong. The number of accessible variables at any given scope in your code should be relatively constant with respect to the size of the codebase. The ratio of stack to heap should likewise level off at some constant level after you get to a few thousand lines or so.


My personal preference is that your in/out defs make me want to smash things indiscriminately. And frankly, I rarely ever have complex objects passed from a public scope without some sort of smart pointer usage; the entire discussion strikes me as a little bit alien in this day and age.

Share this post


Link to post
Share on other sites
Only value that can ever come from conventions is consistency. So if code consistently uses IN/OUT notation, then it has a benefit. If it uses const in some cases and IN in some cases, it's just a waste of effort.

Of course, mixing C++ libraries makes consistent conventions impossible.

what practical purpose is there for constness other than telling the compiler and a potential programmer that the first two arguments are not going to be modified?[/quote]
It tells the programmer, compiler essentially ignores it.

Benefit of const-correctness are that it propagates. It can warn about unexpected implicit conversions or inadvertedly modifying const or supposedly const members. Stock compilers don't detect all such cases, but PCLint does. I've found a few surprising cases of modifying a value I would have thought would be const.

What are your personal preferences and, more importantly, why?[/quote]
All things being equal, I'd have to lean just a few percent towards pointers. While it tends to be less intuitive in C++ than references, there is a handful of edge cases with alignments and some obscure pointer stuff where it's more convenient to have pointer notation and optionally decay it into arrays than just references.

There's also an edge case of implicit conversions which aren't possible with pointers. For C/C++ code, pointers also allow __restrict which C++ does not support and therefore cannot be applied to references.

That said, idiomatic C++ uses references and shuns pointer notation.

Share this post


Link to post
Share on other sites


what practical purpose is there for constness other than telling the compiler and a potential programmer that the first two arguments are not going to be modified?

It tells the programmer, compiler essentially ignores it.
[/quote]

For passing arguments, you want a const reference for objects.

See GOTW 81 for discussion about const parameters and the optimizer.

Also, there is GOTW 88, "the most important const", which is a good read.



That said, there are many values to const generally.

As GOTW88 points out, const can be required to keep temporaries alive.

const objects and const variables, those that are declared const and known at compile time, can be used directly at compile time. For example:
const int NUM_ITEMS = 42;
The compiler can (and will) use the knowledge that it is const to unroll loops, vectorize loops, will encode it directly into the opcodes instead of loading it at runtime, and it can find and eliminate dead code at compile time. It can use this knowledge for strength reduction, common case detection, locality optimization, and cache effect calculations.

Similar things are true for objects. If the compiler knows it is const and knows the value it contains, itcanimprove the code in those same ways. It can make assumptions about side effects to calling functions. It can make assumptions about object lifetimes. Etc., etc.



The difficulty here is that const propagates. In order to get the benefits of local const objects, the object's members need to respect it and functions you call need to respect it. Just make your code const correct in the first place and avoid the hassle later on.

Share this post


Link to post
Share on other sites

- what's with passing everything by reference? So many times I've found myself going over code that passes everything by reference and I've never seen an actual benefit to it. Unless you have an object on the heap (which you do more and more seldom as your code grows) or an already dereferenced object (which would have required a validity check at some point anyway), then you've gained nothing compared to having to reference an object that's on the heap (which costs nothing) and conversely not having to dereference an object on the stack (which doesn't mean you don't need to check the pointer for validity). In both cases, at the end of the day, it boils down to zero loss or zero gain either way. To illustrate, to me the following are functionally equivalent. However, the first one is shorter and more clear:


Passing by reference does have it's benefits. You save on the memory foot print that passing by value would add, and that could add up if it's a frequently called function. Making them constant servers two purposes. Some programmers are under the assumption that this will allow the compiler to optimize their code. Some compilers might but most do not. The real reason is that if you are assuming that the value will not change and your wrong about your assumption then if you have a smart compiler then it hopefully will throw an error and say that the constant value changed.

However, passing by reference has the obvious risk of that API you're using might actually change the value when you weren't expecting it to.

I personally prefer how Java handled the pass by reference or pass by value. They established a set of rules that cannot be changed. This forced programmers to do something that they should have done in the first place, and that was to just be consistent. As long as you and your team are playing by the same rules you're fine. You only really start to have problems with coding conventions when people start playing by different rules.

Share this post


Link to post
Share on other sites
I would write the function declaration as:

void BuildMatrix(const IModel& skeletons, const IMatrix4x4& matSrc, IMatrix4x4 *matDst);

That then makes it clear to me which parameters are in and out when looking at either the calling code or the function declaration, because out parameters are pointers, and input parameters are const.

Share this post


Link to post
Share on other sites
I view both Adam_42's method and Madhed's method as valid, but the First option would allow for Object Recycling to save on time allocating another IMatrix4x4. However recycling object can be dangerous if it wasn't anticipated that the object being passed might already had data allocated to something other than the defaults. Object Recycling can save time, but only if you're dealing with a lot of objects. On a small number of object the recycling logic could easily eat into any performance gains..

Share this post


Link to post
Share on other sites
Fair point. I'd argue however that the pointer method is more error prone as that one could be called with a null pointer and thus leading to crashes.
In the given case i find it better to return a temporary. With C++11 and move constructors it gets even better.

non-const reference or temporary return value makes sense here methinks.

@irreversible: Btw, why IMatrix4x4? Is this an interface class?

Share this post


Link to post
Share on other sites

I'm not going to bring up the pass-by-reference arguments, I'm pretty sure everybody has been talking about it. But here's what I don't like with your code:

void BuildMatrix(IN IModel * model, IN IMatrix4x4 matSrc, OUT IMatrix4x4& matDst);

[font=helvetica, arial, verdana, tahoma, sans-serif][color=#282828]To you, it might make sense, but to others, the IN and OUT symbols make your code less readable. To somebody who hasn't read the #defines, those INs and OUTs will confuse them. What are those? Meaning, they have to search the code for the definition of IN and OUT.[/font]



[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]#define IN[/font]


[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]#define OUT[/font]



[font=helvetica, arial, verdana, tahoma, sans-serif][color=#282828]So through the magic of IDE nowadays (vim/texteditor users would get terribly frustrated and angry at you at this point by the way), we could say they found them rather easily, but empty spaces? What does that mean?[/font]



[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]So now, they are going to have to read the inside of your method to know what it is (BAD!), or knock on your cubicle and bug you. "Hey, I see INs and OUTs, what are those?" That's one developer. You multiply that to 10 if you are working in a relatively large-scale project, you get 10 people bugging you and asking you (probably debating you) the purpose of INs and OUTs.[/font]




[color=#282828][font=helvetica, arial, verdana, tahoma, sans-serif]That's why it's bad.[/font]

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!