C++ Const Keyword - Use Liberally

C++ const keyword - use liberally?

This, IMHO, is overusing.
When you say 'const int age,...' what you actually say is "you can't change even the local copy inside your function". What you do is actually make the programmer code less readable by forcing him to use another local copy when he wants to change age/pass it by non-const reference.

Any programmer should be familiar with the difference between pass by reference and pass by value just as any programmer should understand 'const'.

In C, is it legal to add `const` only in function definitions, not declarations?

This is explicitly allowed by a special case in the rules for function parameter lists. N1570 §6.7.6.3p131 says:

In the determination of type compatibility and of a composite type, each parameter declared with function or array type is taken as having the adjusted type and each parameter declared with qualified type is taken as having the unqualified version of its declared type.

But you must also understand that the "unqualified version" of a type like const char * is still const char *, because the type const char * is derived from the type const char, and §6.2.5p26 says

A derived type is not qualified by the qualifiers (if any) of the type from which it is derived.

That means that the declaration

void foo (const int x);

is compatible with the definition

void foo (int x) { ... }

but the declaration

void bar (const char *x)

is not compatible with the definition

void foo (char *x) { ... }

You might be wondering why these rules are the way they are. The short version is that in C, all arguments are always passed by copying the value (but not any data pointed to by the value, if there are pointers involved), so it doesn't matter whether an actual argument is const T; the callee receives it as a regular old T regardless. But if you copy a pointer to constant data, the copy still points to constant data, so it does matter and that qualifier should be preserved.


1 Document N1570 is the closest approximation to the 2011 ISO C standard that is publicly available at no charge.

To the best of my knowledge, these rules have not changed significantly since the original 1989 standard. Pre-C89 "K&R" C didn't have prototypes, nor did it have const, so the entire question would be moot.

Using const on local variables

Personally, I like to use const for any declared object (I'm not sure the word "variable" applies) unless I'm actually going to modify it. It documents to the reader that the value of the object is always going to be whatever it was initialized to, which can make the code easier to follow and analyze.

(It can also help the compiler in some cases, but most compilers, when invoked in optimizing mode, are smart enough to notice that the object is never modified. And if you invoke the compiler in non-optimizing mode, you're telling it that you don't care much about performance.)

In fact, if I were going to design my own language, all declared objects would be const (read-only) by default unless you explicitly mark them as modifiable.

Does it ever make sense to make a fundamental (non-pointer) parameter const?

Remember the if(NULL == p) pattern ?

There are a lot of people who will tell a "you must write code like this":

if(NULL == myPointer) { /* etc. */ }

instead of

if(myPointer == NULL) { /* etc. */ }

The rationale is that the first version will protect the coder from code typos like replacing "==" with "=" (because it is forbidden to assign a value to a constant value).

The following can then be considered an extension of this limited if(NULL == p) pattern:

Why const-ing params can be useful for the coder

No matter the type, "const" is a qualifier that I add to say to the compiler that "I don't expect the value to change, so send me a compiler error message should I lie".

For example, this kind of code will show when the compiler can help me:

void bar_const(const int & param) ;
void bar_non_const(int & param) ;

void foo(const int param)
{
const int value = getValue() ;

if(param == 25) { /* Etc. */ } // Ok
if(value == 25) { /* Etc. */ } // Ok

if(param = 25) { /* Etc. */ } // COMPILE ERROR
if(value = 25) { /* Etc. */ } // COMPILE ERROR

bar_const(param) ; // Ok
bar_const(value) ; // Ok

bar_non_const(param) ; // COMPILE ERROR
bar_non_const(value) ; // COMPILE ERROR

// Here, I expect to continue to use "param" and "value" with
// their original values, so having some random code or error
// change it would be a runtime error...
}

In those cases, which can happen either by code typo or some mistake in function call, will be caught by the compiler, which is a good thing.

Why it is not important for the user

It happens that:

void foo(const int param) ;

and:

void foo(int param) ;

have the same signature.

This is a good thing, because, if the function implementer decides a parameter is considered const inside the function, the user should not, and does not need to know it.

This explains why my functions declarations to the users omit the const:

void bar(int param, const char * p) ;

to keep the declaration as clear as possible, while my function definition adds it as much as possible:

void bar(const int param, const char * const p)
{
// etc.
}

to make my code as robust as possible.

Why in the real world, it could break

I was bitten by my pattern, though.

On some broken compiler that will remain anonymous (whose name starts with "Sol" and ends with "aris CC"), the two signatures above can be considered as different (depending on context), and thus, the runtime link will perhaps fail.

As the project was compiled on a Unix platforms too (Linux and Solaris), on those platforms, undefined symbols were left to be resolved at execution, which provoked a runtime error in the middle of the execution of the process.

So, because I had to support the said compiler, I ended polluting even my headers with consted prototypes.

But I still nevertheless consider this pattern of adding const in the function definition a good one.

Note: Sun Microsystems even had the balls to hide their broken mangling with an "it is evil pattern anyway so you should not use it" declaration. see http://docs.oracle.com/cd/E19059-01/stud.9/817-6698/Ch1.Intro.html#71468

One last note

It must be noted that Bjarne Stroustrup seems to be have been opposed to considering void foo(int) the same prototype as void foo(const int):

Not every feature accepted is in my opinion an improvement, though. For example, [...] the rule that void f(T) and void f(const T) denote the same function (proposed by Tom
Plum for C compatibility reasons) [have] the dubious distinction of having been voted into C++ “over my dead body”.

Source: Bjarne Stroustrup

Evolving a language in and for the real world: C++ 1991-2006, 5. Language Features: 1991-1998, p21.

http://www.stroustrup.com/hopl-almost-final.pdf

This is amusing to consider Herb Sutter offers the opposite viewpoint:

Guideline: Avoid const pass-by-value parameters in function declarations. Still make the parameter const in the same function's definition if it won't be modified.

Source: Herb Sutter

Exceptional C++, Item 43: Const-Correctness, p177-178.

Const correctness for value parameters

My take on it:

It's not a bad idea, but the issue is minor and your energy might be better spent on other things.

In your question you provided a good example of when it might catch an error, but occasionally you also end up doing something like this:

void foo(const int count /* … */)
{
int temp = count; // can't modify count, so we need a copy of it
++temp;

/* … */
}

The pros and cons are minor either way.

const vs non-const variable with no change in value once assign

A clever compiler can understand that the value of a variable is never changed, thus optimizing the related code, even without the explicit const keyword by the programmer.

As for your second, question, when you mark a variable as const, then the follow might happen: the "compiler can optimize away this const by not providing storage to this variable rather add it in symbol table. So, subsequent read just need indirection into the symbol table rather than instructions to fetch value from memory". Read more in What kind of optimization does const offer in C/C++? (if any).

I said might, because const does not mean that this is a constant expression for sure, which can be done by using constexpr instead, as I explain bellow.


In general, you should think about safer code, rather than faster code when it comes to using the const keyword. So unless, you do it for safer and more readable code, then you are likely a victim of premature optimization.


Bonus:

C++ offers the constexpr keyword, which allows the programmer to mark a variable as what the Standard calls constant expressions. A constant expression is more than merely constant.

Read more in Difference between `constexpr` and `const` and When should you use constexpr capability in C++11?


PS: Constness prevents moving, so using const too liberally may turn your code to execute slower.

The new keyword auto; When should it be used to declare a variable type?

I think when the type is very well-known amongst the co-programmers who work (or would work) in your project, then auto can be used, such as in the following code:

//good : auto increases readability here
for(auto it = v.begin(); it != v.end(); ++it) //v is some [std] container
{
//..
}

Or, more generally,

//good : auto increases readability here
for(auto it = std::begin(v); it != std::end(v); ++it)//v could be array as well
{
//..
}

But when the type is not very well-known and infrequently used , then I think auto seems to reduce readability, such as here:

//bad : auto decreases readability here
auto obj = ProcessData(someVariables);

While in the former case, the usage of auto seems very good and doesn't reduce readability, and therefore, can be used extensively, but in the latter case, it reduces readabilty and hence shouldn't be used.


Another place where auto can be used is when you use new1 or make_* functions , such as here:

//without auto. Not that good, looks cumbersome
SomeType<OtherType>::SomeOtherType * obj1 = new SomeType<OtherType>::SomeOtherType();
std::shared_ptr<XyzType> obj2 = std::make_shared<XyzType>(args...);
std::unique_ptr<XyzType> obj2 = std::make_unique<XyzType>(args...);

//With auto. good : auto increases readability here
auto obj1 = new SomeType<OtherType>::SomeOtherType();
auto obj2 = std::make_shared<XyzType>(args...);
auto obj3 = std::make_unique<XyzType>(args...);

Here it is very good, as it reduces the use of keyboard, without reducing the readability, as anyone can know the type of objects being created, just by looking at the code.

1. Avoid using new and raw-pointers though.


Sometime, the type is so irrelevant that the knowledge of the type is not even needed, such as in expression template; in fact, practically it is impossible to write the type (correctly), in such cases auto is a relief for programmers. I've written expression template library which can be used as:

foam::composition::expression<int> x;

auto s = x * x; //square
auto c = x * x * x; //cube
for(int i = 0; i < 5 ; i++ )
std::cout << s(i) << ", " << c(i) << std::endl;

Output:

0, 0
1, 1
4, 8
9, 27
16, 64

Now compare the above code with the following equivalent code which doesn't use auto:

foam::composition::expression<int> x;

//scroll horizontally to see the complete type!!
foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<int>, foam::composition::expression<int>, foam::operators::multiply>> s = x * x; //square
foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<foam::composition::details::binary_expression<foam::composition::expression<int>, foam::composition::expression<int>, foam::operators::multiply> >, foam::composition::expression<int>, foam::operators::multiply>> c = x * x * x; //cube

for(int i = 0; i < 5 ; i++ )
std::cout << s(i) << ", " << c(i) << std::endl;

As you can see, in such cases auto makes your life exponentially easier. The expressions used above are very simple; think about the type of some more complex expressions:

auto a = x * x - 4 * x + 4; 
auto b = x * (x + 10) / ( x * x+ 12 );
auto c = (x ^ 4 + x ^ 3 + x ^ 2 + x + 100 ) / ( x ^ 2 + 10 );

The type of such expressions would be even more huge and ugly, but thanks to auto, we now can let the compiler infer the type of the expressions.


So the bottomline is: the keyword auto might increase or decrease clarity and readability of your code, depending on the context. If the context makes it clear what type it is, or at least how it should be used (in case of standard container iterator) or the knowledge of the actual type is not even needed (such as in expression templates), then auto should be used, and if the context doesn't make it clear and isn't very common (such as the second case above), then it should better be avoided.

When do you use the this keyword?

There are several usages of this keyword in C#.

  1. To qualify members hidden by similar name
  2. To have an object pass itself as a parameter to other methods
  3. To have an object return itself from a method
  4. To declare indexers
  5. To declare extension methods
  6. To pass parameters between constructors
  7. To internally reassign value type (struct) value.
  8. To invoke an extension method on the current instance
  9. To cast itself to another type
  10. To chain constructors defined in the same class

You can avoid the first usage by not having member and local variables with the same name in scope, for example by following common naming conventions and using properties (Pascal case) instead of fields (camel case) to avoid colliding with local variables (also camel case). In C# 3.0 fields can be converted to properties easily by using auto-implemented properties.

The best way to avoid using lots of global variables or make them easy to access and modify

Oh dear... Having had the joy of wrestling with a 1000LoC functions (which liberally accessed various global variables) I can sympathize.

First of all, I'd strongly reccommend you to get and read the book "Working Effectively with Legacy Code" by Michael C. Feathers. It will give a lot of good ideas how to work, well, with the code you have.

Secondly: Consider your testing procedures. I assume that the new program is expected to work like the old pascal one, so make sure you have a solid set of tests to verify that.

Finally: There are several ways to get such a global variable blob under control. The best option (in the medium and long term) would probably be to untangle that ball of yarn, pack global variables that belong together into structs or classes, and use dependency injection to get them to the objects (and functions) that need them (i.e. the constructor of such classes demands a pointer to said struct).

//FlagsStruct.h
struct FlagsStruct
{
int Flag_1;
int Flag_2;
}

//WorksWithFlags.h
class WorksWithFlags
{
public:
WorksWithFlags(FlagsStruct* flags);
//...
}

That way you need to create the FlagsStruct once (and only once), then hand it to all those that need to work with it.

And regarding the const values - that is pretty much the only way to handle consts. You could too partition those up into groups that belong together and create individiual header files (with them as static consts in them) for those, but beyond that I don't see much that you could do about them. On the postive side: consts are (being read-only) rather benign "globals".



Related Topics



Leave a reply



Submit