C and C++ notes / Syntax notes

From Helpful
(Redirected from C++)
Jump to navigation Jump to search
Notes related to C and C++

Note: Some notes describe behaviour common to most variants - for C mostly meaning C89 (and a bit of C99), unless mentioned otherwise.

This article/section is a stub — probably a pile of half-sorted notes and is probably a first version, is not well-checked, so may have incorrect bits. (Feel free to ignore, or tell me)


Array stuff

Conversion and casting fun

static_cast

dynamic_cast

When dealing with object hierarchies, this is more robust than static_cast.

Does more checking, is slightly slower. May return a null pointer.

You can sometimes argue that you know a static_cast will do the right thing and use that instead.

const_cast

reinterpret_cast

C-style cast (type)value

Function-style cast type(value)

Pointer related stuff in C and C++

On pointer aliasing

Interesting typing stuff

auto return type

Hairy bits and common misunderstandings

volatile

(verify)

Volatile basically means "this value might be changed by something else than the current thread of execution. Compilers, do not optimize this by keeping it in a CPU register. Fetch it from RAM every time my code refers to it."


This may sound useful for data structures shared between threads, but if you need them to be atomic and safe, volatile will not necessarily guarantee that.


There are specific cases that are fine.

Almost all of these cases are narrowly defined cases, informed cheating, and often center on simple operations on primitive types.

Volatile also has uses in memory mapped IO, and also low level code such as interrupt handlers, and some kernel guts, some embedded code.


...but many argue that until you can explain succinctly why your case is not one of the bad cases, you should probably not use it.

In part because in more complex cases, you may just be throwing in the towel about transactional correctness, races, and speed, possibly all at the same time.



Chances are a regular progammer won't need it much, possibly ever (in part because pointers are a different story anyway - see pointer aliasing).

const

This article/section is a stub — probably a pile of half-sorted notes and is probably a first version, is not well-checked, so may have incorrect bits. (Feel free to ignore, or tell me)


const was intended as "I'm not planning to change this. Compilers, you can assume it won't change, if that helps you any". (e.g. avoid copying it from stack on every reference, or passing it by reference instead of by value if it is large)


But it isn't.


In practice, const is an informal statement about what you won't do with the value, and only in the scope you declare this in in.

const variables have to be assigned at declaration time, and then can't be reassigned - it's it's a compilation-time error when you try to alter the contents of a variable declared const.


But beyond that, the issue is roughly that constness is part of the type in a scope, not a property of the thing it points to.

For globals, that doesn't matter much.

For function parameters, it does.

One of the issues is that you can cast away constness and violate nothing in your scope, while violating someone else's constness promise.

Put another way: while trying to alter it directly is a compile time error, altering it indirectly is possible, and gives undefined behaviour.


And people do add and remove constness, e.g. when APIs demand it via their argument types. Which can result in code requiring a promise that unrelated other code cannot keep. Maybe it's fine today, but it may become a very well hidden bug later.


Also, note that const is effectively about the binding and reassigning, not about (im)mutability of the object you are referring to.



Long story short, const variables cannot be considered immutable.

You can often get away with doing so, but it is not guaranteed.

And compiler builders know this, so they won't use it to optimize much.


(actually, modern compilers are likely to put const variables (which includes string literals because their type is something like char const *) in a read-only data segment, which may be mapped into the process to be read-only, and altering them would be an access violation)


Even if it did nothing, constness is useful for other reasons, as signalling towards other programmers of what you're likely to (not) do.

You can avoid some basic mistakes, you can avoid some misunderstandings of an API, and is to some degree a self-documenting thing.

It can also be a good reminder to yourself, e.g. that you want your API to always make a copy. (In practice that has limited value - pass by reference usually leads programmers to explicitly document responsibilities, and pass by value doesn't have the issue in the first place)


Many people avoid const, because they know it as a "the compiler whines at me unless I add more casting" thing.

And most, including me next week, are a little fuzzy on the important details, opting to know little more than that it's safe to make the compiler happy when it complains about libc string function stuff.


Const literals may be effectively read-only.



const and pointers

Consider

  • const values
mostly things within a scope
  • const pointers/references
which implies that said binding will not change within a scope
not about the value it points to, that can change as much as it wants
  • then there are const pointers to const values
  • Constness in value declarations
...particularly in virtuals...
are predeclared constness that things implementing an interface have no choice but to adhere to
because of the compiler hissyfit if your implementations try to
also useful to e.g. signal your interface/API that you intend to have no side effects.
Not that they couldn't, but it's useful to signal intent of the architecture.


Const and scope

  • Const globals arguably make more sense
const  unsigned char  byte_mask = 255; 
  • constness of a function-local variable is pretty clear
  • but constness of the function parameters are a little further-reaching


Implications of use

In theory, there are two major ideas about how const could help things.

One is that the compiler could make more assumptions about data not changing, and could optimize by not needing to copy as much data in some types of operations.

However, const variables cannot be safely considered immutable (more explanation below), which means the compiler can do only some minor optimizations.

It is also not a strong enough guarantee to be useful in parallelism.


The other idea is safety between functions, which could be useful in interfaces, to keep people from doing things they shouldn't be doing.

This is arguably the best use of const: signaling to other coders (reading the function definitions, or waiting for compiler errors) that they are not supposed to alter something, so they can not no so accidentally, only subversively. Resulting bugs (or features) can be attributed to the subverting programmer.

For example, it lets you pass by reference with a little more peace of mind.



More detail

In particular pointers make things more complex.

Consider you can declare both a const pointer and a non-const pointer to an actual value. You couldn't change the value via the const one, but you can via the constless variable -- which breaks the contract of the const variable.

You liekly wouldn't do that within the same scope, but it easily happens when you have functions are declared with constness in their parameters (e.g. printing or logging a given string).

You can also just cast the constness away. You have to do so explicitly, and if you understand all of the details you'll know it's a bad idea, but you can.



Syntax

The syntax can be confusing, particularly when you mix in pointers, and also declare that they are const. For example, what's the difference between:

char *s;
char const *s;
char * const s;
const char *s;
char const * const s;

To read those, the simplest rule (of thumb) is to read from right to left. Respectively:

  • s is a (non-const) pointer to a char
  • s is a (non-const) pointer to a const char
  • s is a const pointer to a char
  • s is a (non-const) pointer to a const char (same as the second)
  • s is a const pointer to a const char

Const applies to the thing at its left, unless there is nothing there, then it applies to the thing at its right. This is why const char and char const cases above are equivalent. In general it's probably least confusing to write and rely on the at-the-left default. Easier to read.


It also helps to know what happens with C++'s &-type passing. Well, basically what you would expect: you can pass an object without copying it it, and also say that it shouldn't be altered. Quite comparable to using a pointer to a const object.


Note that const can sometimes be a little safer than using #DEFINEs.


Casting away

Casting away constness can lead to undefined behaviour in some cases, because of compiler optimizations. (also depends a little on scoping details)

Consider:

const int a=1;
int   *b = (int *)(&a); //take address, make a pointer via which we can alter it.
*b=2
cout << a;

You might expect this to print 2, and without the const it would. With the const it most likely prints 1, because the compiler assumed that a is 1 by definition and can bake that value into any of the uses (most of those things: ...at this scope).


Note: It's stricter style to do that cast like:

int   *b = const_cast<int *>(&a);

...in that with this, the compiler will complain if the cast changes anything other than the constness (or volatileness). It's otherwise equivalent, so in the above example it would mean the same error. It simply doesn't make sense to try the above. Or to do it in functions called from a scope that assumes constness, for much the same reason. Casting away constness should probably only be done when you can come up with a convincing reason.


Further notes
  • a string literal has type "array of n const char"


See also

Pointers versus references

string stuff

string literals