Learn c++
This is an excerpt for : visit
chapter 0
chapter 0 was done without any comments needed.
chapter 1
initialization
3 forms of Initialization :
- int a = 11.1; // (copy) only non-explicit constructors are called
- int a(11.1); // (direct) any constructor is called if it matches
- int a{11.1}; // (uniform) this is checked, and this one will fail !
non-explicit means all constructors without the 'explicit' keyword see chapter 9 !!
The uniform initialization sees that this would be a narrowing conversion and fail. The others perform default conversions and make a=11.
Important
favor uniform (brace) initialization when possible. Beware it is checked and thus the slowest of the three.
Note that brace initialization can handle more complex structures as well.
Adding a '=' does not make it copy assignment !
zero initialization
| zero initialization | |
|---|---|
Important
Since C++11 and the introduction of move semantics, we now talk about copy construction and copy assignment opposite move construction and move assignment. (chapter 9)
multiple initialization
When using pointers, you need to put an * on all variables :
Important
Just always put each declaration on one line, initialize it and comment what it is used for.
uninitialized variables
Global and static variables are initialized to 0 because it can be done at compile time without overhead to the running program. Other variables would impose an overhead and so contain whatever is in the assigned memory at that time.
Warning
Some compilers DO initialize variables when running in debug mode ! NOT always with 0 by the way !!, again a reason to always initialize !
Important
Use -Wuninitialized (or -Wall) to make the compiler detect it.
identifiers
Important
best practice is naming variable in lowercase, and functions in either dromedaryCase() or with_under_score()
The compiler reserves names starting with underscores
operators
Did you know ? That the number of operands of an operator is called it's 'arity' : un-ary, bin-ary', tern-ary'.
expressions
Important
expressions always evaluate to 1 value, and expressions are always part of a statement.
If an expression is followed by only a semicolon, it becomes an 'expression statement'
| expression statement | |
|---|---|
It is true that the value of an expression can be used in another expression, but the value of the complete 'expression statement' is discarded ! So above y becomes 5, but the remaining 5 is discarded.
chapter 2
functions
This is valid code :
It prints nothing, f is just evaluated as the pointer to f() and discarded.
Important
C++ does not have nested functions.
Also functions evaluate to expressions so this is valid:
| functions evaluate to expressions | |
|---|---|
Important
In c++ the second void in "void x(void)" should be omitted. It is still allowed but better is void x().
argument evaluation
The order of evaluation in C++ is NOT defined !!
| argument evaluation order | |
|---|---|
This prints :
This seems to prove that g++ evaluates from right to left, judging by the order of prints.
However this program proves that g++ handles different orders even within the same program :
How can you explain this apart from this :
- in the first call y++ is evaluated first, it pass 10 and then increments
- the y is then evaluated and is passed as 11 # 10 11 return 21
- in the second call y is not the first one to be evaluated !!, it should have been 10 !!, the only way to get these values is if y++ is again first evaluated !!
So x = f(y++,y); is left to right !! and x = f(y,y++); is right to left !!
This comes from visit.
Important
The compiler can evaluate operands and other sub expressions in any order, and may choose another order when the same expression is evaluated again.
Warning
never rely on arguments being evaluated in a particular order.
local variables
Local variables are destroyed in opposite order of creation when they leave scope.
scope and lifetime
Important
scope is a compiletime property, lifetime is a runtime property.
A small example of where scope is different from lifetime :
x's lifetime is from line 1-8, x's scope is line 3-4. Another example is a function call: a local variable is out of scope during it but it's lifetime remains intact after the function returns.
declarations
They are properly called "forward declarations".
When you declare a function but provide no definition, it can still compile if the function was never called. It is like including a header file, the functions you never call are never resolved by the linker.
one definition rule
Important
mainly : any function, object, type or template can only have one definition.
However it is more subtle than that, so there are 3 rules.
- within a file a function,object,type or template can only have 1 definition
- within a program a normal function or object can only have 1 definition
- within a program types, templates and inline functions can have multiple definitions but they have to be the same !
Important
all definitions also serve as declarations. So you actually only need forward declarations.
Important
forward declaration that are NOT definitions are also called 'pure declarations'
namespaces
The general format for defining a namespace is
And calling those names :
You can also directly use the nested format so this is all legal :
| nesting | |
|---|---|
It will print as expected :
Important
::name defaults to using the global namespace, so name is ::name.
Important
You can use namespaces multiple times, they just get combined into one.
But of course you cannot use the same name within those separate blocks.
using
I usually avoid using 'using' ;) but if used note that it obeys block scope and you don't override a previous using but they add up.
Important
also you cannot un-use a namespace !
| using | |
|---|---|
If you include a complete namespace there will be more conflicts:
Try to favor using declarations (using x::y). They are more fine grained
Important
Try to avoid using directives (using namespace X).
Important
Try to avoid using declarations (using X::Y) in the global scope.
preprocessor
One note: the tutorial is WRONG about 'object-like macro's don't affect preprocessor directives. :
| preprocessor | |
|---|---|
This program prints nothing, if you would #define FOO 9 it would print 9 !
header files
c++ system header files do not have a .h extension because to differentiate them from the original files that did not use the std namespace.
Important
files without .h use the std namespace. Files beginning with c (cstdio, cstdlib) are still useful C functions.
include order
cpplint says :
- system C++ first
- system C second
- all the rest last
Normally you should include all headers a source file needs in that sourcefile, but a put all headers in the same file to be able to use precompiled headers.
Here is a quick test te see if we can mix this...
| include headers | |
|---|---|
This is about half the time. Now i disabled backend.hpp and restored all includes. The time in this setup becomes :
Actually faster so the best approach is to just 'gather' all used includes in one main header file, precompile that header and include it before any other header. It is just as fast and the includes form a backup and documentation. Also watch the parallel compile, it is 4 times quicker !
header guards
Important
Header guards make sure a header file is not included more than once. The file is still included in each header file (once).
Why not just avoid definitions in header files !? Because we need types to be accessible from multiple source files.
Important
Header guards are there mainly for class and struct definitions !! Also for enums,etc.
chapter 3 debugging
Ye... nothing new .
chapter 4 types
Important
std::nullptr_t is a fundamental data type since C++11
Also since C++11 char types come in different sizes : char, wchar_t, char8_t (C++20) char16_t and char32_t.
int16_t etc are NOT fundamental types.
integers
Officially you can declare a long as :
| long int | |
|---|---|
But it is advised to drop both the signed and int because they are defaults.
Important
Signed integer overflow will result in undefined behavior.
So you cannot expect that assigning or adding resulting in overflow will end up in a certain number! This is probably because compilers may choose where to put the sign bit in integers (1's/2's complement).
unsigned integers
When you assign 280 to an unsigned char it is NOT overflow but out of range or wrap around. This results in truncation.
Important
Out of range values (unsigned int) do behave predictably, the excess bits are just lost !
Important
google thinks unsigned integers should be avoided.
The reasons are somewhat cloudy, but mainly in subtraction you don't get negative numbers but huge numbers. On the other hand i don't see much advantage in using unsigned either. In any case do not mix signed and unsigned.
Important
in the standard library some containers use an unsigned integer as loop iterator ! Watch out when using auto.
integer sizes
Important
The fixed size int8_t, int16_t etc may not be defined on all architectures.
There is a set of integers that can be used to get the fastest and smallest integer that has at least x bits. int_fast8_t, int_fast16_t, int_fast32_t, and int_least8_t, int_least16_t, int_least32_t.
For instance you could ask for int_least16_t and get a 32 bit integer if the system has no 16 bit ints.
Best seems to just test your production system for the best int to use. A simple program for this :
On linux this gave me :
| output | |
|---|---|
So you see even 2 byte ints are better represented by long, so in this case i used long integers.
Since i thing int_fast32_t is just a bit to long(12 chars) in code i fixed this by using this typedef, since it seems int_t is not defined yet.
| fast integer | |
|---|---|
Important
In contrast to the int_xx_t types, the fast and least types are guaranteed in c++11.
small integers
Due to an oversight in the c++ specs the small integer types std::int8_t and std::uint8_t sometimes are treated as char values :
| small integers | |
|---|---|
You would expect this to print 65, but it prints 'A' instead.
Important
Don't use the small int versions, just go for char directly then you know what to expect.
size_t
This is simply the type to use when handling sizes ( size_t sizeof(type); ). It is guaranteed to be unsigned and at least 16 bits.
floating point notation
Also scientific notation. The main format is
Important
The general form is "significand x 10exponent" in C++ this becomes sEe
By convention we always write 1 digit before the decimal and the rest after.
So the mass of the earth is 5,973,600,000,000,000,000,000,000 Kg
Written as \(5.973x10^{24}\)
This will be written like this in C++ 5.973000e+24 (6 digits) if you print it with %e. std::cout has a default of 5 digits :
| float | |
|---|---|
Output :
So note that the number of digits is default and does hide some significant digits (this 6 significant digits is compiler specific !) setprecision from iomanip can be used to alter that.
Important
std::cout will not print the decimal part if it is 0. So 5.0 becomes 5 !!
sizes
| name | min size | typical |
|---|---|---|
| float | 4 | 4 |
| double | 8 | 8 |
| long double | 8 | 8,12,16 |
Important
The default for floating point literals is double, use f to specify float literals.
Important
Comparing floats means comparing bit by bit. This means precision mismatches will very easily go wrong.
rounding errors
Note that seemingly precise numbers (to a human) are not always precise in floats.
| rounding double | |
|---|---|
We expect c to be a rounding problem but d can also not be exactly represented in 16 bits !!
Warning
Never use floating point numbers for financial or currency calculations. Use gmp, boost/multiprecision or some other arbitrary precision library.
Nan and Inf
These are different for different compilers, for instance this program :
Prints this on MSVC :
And for g++ :
Inf stands for infinite, Nan for not-a-number and IND for indeterminate. You can't compare this with a literal, but you can use functions like std::isnan() from cmath. There is also a std::mininf() but this :
Important
Returns whether x is an infinity value (either positive infinity or negative infinity).
You can compare it to 0 however to get which one it is :
boolean
As in C booleans just contain an integer 0 or 1 for false and true. You can print the symbolic version like this:
| booleans | |
|---|---|
output
Just like other ints uniform initialization checks for narrowing conversions so :
char
Important
char is by default signed, so the ascii charset contains characters up to value 127. All characters below 20 (space) are unprintable, also the negative ones.
wchar_t
This is mainly a windows/msvc type and it's size is implementation defined. So do not use this type. When you need to use unicode better use char16_t and char32_t because these sizes are defined hard.
literals
In C++14 the binary format was introduced, in C+11 these did not exist yet:
| literals | |
|---|---|
The separator is also only C++14 and up :
| 1000 separator | |
|---|---|
number formatting
If you want to change format, you can use these constructs from iostream :
But strangely std::bin was not added. You can however print these using bitsets. Also an example of not using a type but a quantity:<8> as template parameter.
magic numbers
Magic numbers are plain numbers without context in code.
| magic number | |
|---|---|
Here we have no idea where this 30 came from, probably it is 30 student per class but it is so much clearer if you do this:
| clearer | |
|---|---|
Note that all three can be evaluated at compile time so they can (should) all be a constexpr.
Important
use constexpr for const values known at compiletime and const for dynamic const values.
const and constexpr
you can add const before or after the type, but starting with const is preferred
| constexpr | |
|---|---|
symbolic constants
We got two flavours :
-
define CONST1 11
- constexpr const1 11
The second one is preferred, with 3 good reasons :
- macro's don't show up as values in the debugger.
- macro's are replaced in the code prior to compilation. This could mess with the code in hard to sport ways.
- macro's do not follow scoping rules. They do follow file scope when defined inside a source file.
That last one might actually be an advantage because it is very predictable.
chapter 5 operators
integer division
Before C++11 negative integer division could round either way, but that is fixed since then.
Important
Integer division drops the fraction so -7/4 is -1 (it is not rounded down but towards 0)
If you really want the fraction in an integer division you need to static_cast at least one of the operand to float, because the type of an expression is that of the widest type.
The remainder is also made predictable in c++11 :
Important
The remainder of x%y always has the sign of x, and also works towards 0
| modulo | |
|---|---|
increment decrement
++ in front of a variable increases the variable and returns the result. Technically this can't be done with the postfix versions.
Important
x++ means a copy of x is made, then x is incremented, and the copy is evaluated and becomes the value of the postfix increment expression.
This also means that prefix operators are better performant than postfix ones.
Important
Since the prefix version is simpler AND more performant, try to only use that.
This also means that prefix operators are better performant than postfix ones.
operator operands
Though all operators have a well defined order of evaluation that does not mean two operand are evaluated in a defined order.
| operands | |
|---|---|
It could be (10 * 12) * 10 or (11 * 11) * 10.
If left is evaluated first a++ is 10 at evaluation, and 11 afterwards. ++a is then incremented first : 12. b is always 10. If right is evaluated first, ++a is 11 at evaluation, if then a++ is evaluated it first evaluates to 11 and then increases to 12.
Warning
In general do not use side effects on the same variable in either function calls or expression. The result is undefined !!
comma operator
The comma operator is the lowest in the list, even lower than assignment. That is why you almost always encounter in between parentheses.
The tutorial says the for loop statement is also a comma operator list, but i doubt that since the first (initializer) statement is only executed once.
Todo
can you find a definitive answer to that !?
Important
The parameter list of a function is NOT a comma separator list as in operators !!
division by zero
Note that 1/0 is not guaranteed to crash your computer, for instance on gcc :
| /0 | |
|---|---|
Both times the compiler will complain but compile reluctantly.
conditional operator
The ?: operator has very low priority, almost anything apart from assignment wins so best use parentheses a lot.
However :
| conditional | |
|---|---|
Important
Note that the operands after ? and : evaluate as if they were parenthesized, so they can be left alone.
Warning
always use this format ( (x<y) ? a : b)
conditional const initialization
I used this myself lately, you can not assign a const in an if statement because of the scope:
myconst will expire in the if statement, but this will work:
| this works | |
|---|---|
Note that at first i made a typo which marks another detail about conditionals. They are expressions, so this missing = becomes a direct initialization :
| error | |
|---|---|
floating comparison
Warning
Never compare floating point values on equality.
If it is needed you want a 'close enough' comparison. Note that it is best to devise a function that compares within a percentage of the largest of the two values. These are called epsilon (small) values or functions. And you need the largest because that number will be the one that has the biggest 'fault'.
| float compare | |
|---|---|
Still this may fail if numbers become really small (approach 0.0) you may also need to add an absolute check as well for the small numbers.
logical operators
There is no logical XOR in C++ :
| no logical xor | |
|---|---|
But if you use booleans you can mimic the XOR with !=
| mimic xor | |
|---|---|
chapter O bit manipulation
std::bitset ...........
C++ has the bitset type for expressing bits like bitfields in C :
| bitsets | |
|---|---|
Note that sizeof(myset) is 8 BYTES, don't be fooled by thinking it is 8 bits. This is just the minimum size for a bitset (long int). You will see that std::bitset<64> is still 8 bits, 65 will take 16 :
Output is :
| output | |
|---|---|
65 bits just do not fit in 8 bytes, so it needs two longs (16 bytes) and at the next wrap (129) it will become 24 bytes. So this does present a better solution for bit fields since you would have to name each field in a bitfield struct differently :
| bitfields | |
|---|---|
Important
One of the few cases you need unsigned int is while doing bit manipulation like this.
At the minimal advantage that this struct will be 1 bytes in size.
Important
A bitset is only larger in granularity (8 bytes).
Besides that bitset offers some convenience functions like :
- test()
- set()
- reset()
- flip()
bitwise operators
Note that left and right do what you would expect in base 10, so the rightmost bit is the least significant. Shifting also follow those directions:
| bitwise operators | |
|---|---|
This will print 23456780 two times. Note again that std::bin does not work so i did a hex example instead.
Important
Also note that ^ (xor) does exist for bitwise operations.
Note that the bitwise operators also work on bitset<>. This can be handy to manipulate multiple bits at a time. bitset operators only handle 1 bit at a time.
All fine :
Important
So it seems you can mix plain integers with bitset<>s.
two's complement
Important
two's complement negative numbers are the inverse of the positive numbers with 1 added
This +1 eases arithmetic and prevents 0 having two representations (all 1's and all 0's).
This means these are the border values for chars :
| val | base | inverted | +1 (-val) | so |
|---|---|---|---|---|
| 0 | 00000000 | 11111111 | 00000000 | 0 == 0 |
| 1 | 00000001 | 11111110 | 11111111 | 1 != -1 |
| 127 | 01111111 | 10000000 | 10000001 | 127!=-127 |
| 128 | 10000000 | 01111111 | 10000000 | -128==-128 |
The sign bits are the leftmost bit, so 128 becomes -128 not 128
Warning
There is a proposal to make two's complement mandatory in C++, but until that it is still compiler dependent.
Important
However gcc, msvc and llvm use two's complement !!
chapter S4 scope and more types
global scope
You can use the global namespace operator to use a global name that was masked.
| global scope | |
|---|---|
coercion
Implicit type conversion in assignments and expressions can lead to weird results :
| prints 4294967291 | |
|---|---|
This does not print -5, but 4294967291. This has to do with the rules of coercion
Evaluating arithmetic expressions
Important
in general variables are promoted (floating types) or widened(integer types) to larger types when needed.
Important
If the operation will convert a larger type to a smaller one it is called conversion.
| arithmetic expressions | |
|---|---|
When evaluating expressions, the compiler breaks each expression down into individual sub expressions. The arithmetic operators require their operands to be of the same type. To ensure this, the compiler uses the following rules:
If an operand is an integer that is narrower than an int, it undergoes integral promotion (as described above) to int or unsigned int. If the operands still do not match, then the compiler finds the highest priority operand and implicitly converts the other operand to match. The priority of operands is as follows:
- long double (highest)
- double
- float
- unsigned long long
- long long
- unsigned long
- long
- unsigned int
- int (lowest)
Chars and shorts are promoted already in the first steps, which leaves this priority list exactly from the one with the 'most room' for a positive number.
So in the case in the introduction :
- 5u - 10
- == with types :
- unsigned int - signed int
- == the highest priority wins, so 10 get's promoted
- unsigned int - unsigned int
- 5u - 10u = -5u
- == -5 unsigned wraps around to 4294967291
So ...
Warning
be careful with mixing signed and unsigned integers.
If in doubt you can use typeinfo to see what the type is. It might be worth noting that g++ uses these codes for the types :
- signed int : i
- unsigned int : j
| typeinfo | |
|---|---|
So both operands 5u and 10 fit into an unsigned, but the answer does not.
enum
enums will evaluate to a plain integer, but it does not work the other way around.
| enum | |
|---|---|
Important
Each enumerated type is a distinct type .
So you also cannot assign a value from one enum to another. You can use smaller types for enums to save space :
| enum | |
|---|---|
enum classes
Enums are not typesafe, for instance you cannot assign a value to another enum but comparison just evaluates to integers :
Though the compiler does complain in the above example, it is just a warning. If you use 'enum class' it will not compile at all, because now you have to use the scope operator to reach the values :
Important
'enum struct' can also be used instead of 'enum class'
Warning
enum classes have no implicit conversion to int, so you cannot use them as array indices !!
I think in this case it does not pay off using enum classes.
typedef and aliases
There is a new construct for defining type aliases in C++11 and later.
In this format it does not seem as much of an improvement, but :
Here the name is extracted to the left leaving it slightly more readable.
structs
Note that in C++ you don't have to use struct for every declaration :
| structs | |
|---|---|
Note that whenever you use ={} or {} all variables you don't specify are made 0. If you don't use {} at all everything is uninitialized.
You can also since C++11 specify the values in the struct :
| with values | |
|---|---|
Now all members you don't specifically fill are given those values, also when you don't initialize !!
Warning
This only counts for C++14 and up.
auto
auto is used to deduce the type of a variable from it's assignment expression. From that point on however the type is fixed.
chapter C.5 control flow
Not much...
chapter 6 arrays
fixed arrays
Declaring an array with dimensions should only be done with a compile time constant.
| fixed array | |
|---|---|
Warning
That last one does work on g++ but is NOT standard c++ !
Arrays are not initialized by default because of performance, but using {} will set them to 0. Explicit initialization :
| nullify | |
|---|---|
passing arrays
Arrays are never completely copied but passed by reference. So if you don't want a function to modify the values pass it as const.
| by reference | |
|---|---|
Important
Arrays are always converted to pointers when passed to function.
| by reference | |
|---|---|
Plus y and z will get a warning like :
Warning
‘sizeof’ on array function parameter ‘z’ will return size of ‘int*’
Important
Note that the size of an int here is 4 bytes but the size of a pointer to int is 8 bytes !!
array length
The ole way of dividing sizeof(arr)/sizeof(elm) still works, but c++17 introduced std::size() and better, signed in std::ssize() which returns the number of elements.
std::ssize() also works on std::vector and std::array so why not use that consistently.
Important
use std::ssize() to get the number of elements in array like datatypes.
Warning
std::[s]size() is only available in c++17 and up. For std::array and std::vector you could use .size() before c++17. For [] you will have to do the sizeof trick.
sorting
The std::sort() function does work on arrays and on earlier compilers.
multidimensional arrays
They order in which you expect (row-major order) : int array[row][cols];
| multidimensional | |
|---|---|
Internally the array is contiguous, so this just works. However if you would make this an array of int [6] you would have to do the row/col math yourself.
Important
you can only leave the first dimension open to be calculated by the compiler.
This seems counter intuitive but if you draw out the array format it all makes sense.
| layout | |
|---|---|
std::string_view
If you create a string from another string.
There are three copies of "Hallo", the string literal, the value of s and the value of str. They cannot point to the same data because they might get changed.
Important
Might is used on purpose because const std::string would ALSO get a copy !
To save space in cases like this you could use string_view() which is view only as you may have guessed.
Important
string_view is c++17 and up
| string_view | |
|---|---|
If we used std::string, all three pointers would have a different value but now they are the same, which also means the value can be changed ("Hello->Hallo");
Other operations on the string_view window :
- remove_prefix(1); // "allo"
- remove_suffix(2); // "al"
Warning
But the window only closes !
pointers
Important
When declaring put the * next to the variable name, except for function return types. There it is clearer near the return type.
Important
The & operator does not return an address but returns a pointer holding an address, with it's type derived from the argument. (int * for example).
Important
once assigned (int *ptr = &val) : ptr is the same as &val, and *ptr is TREATED the same as val.
nullptr
C define NULL as a macro for 0, but there is a better way using nullptr.
nullptr is also 0 but it has the correct type for the pointer you use it for. For instance this snippet
| nullptr | |
|---|---|
Will print
And if you feed those to c++filt :
Neat, whenever you encounter an old NULL, just replace it with nullptr.
Then there is also a std::nullptr_t version and that is type that is only capable of holding a nullptr. The only use is probably when you want a function parameter to exactly be a nullptr type !?
usage
- Arrays are implemented using pointers. Pointers can be used to iterate through an array (as an alternative to array indices) (covered in lesson 6.8).
- They are the only way you can dynamically allocate memory in C++ (covered in lesson 6.9). This is by far the most common use case for pointers.
- They can be used to pass a large amount of data to a function in a way that doesn't involve copying the data, which is inefficient (covered in lesson 7.4)
- They can be used to pass a function as a parameter to another function (covered in lesson 7.8).
- They can be used to achieve polymorphism when dealing with inheritance (covered in lesson 12.1).
- They can be used to have one struct/class point at another struct/class, to form a chain. This is useful in some more advanced data structures, such as linked lists and trees.
pointers vs arrays
An example:
| arrays and pointers | |
|---|---|
Important
It’s a common fallacy in C++ to believe an array and a pointer to the array are identical. They’re not. In the above case, array is of type “int[5]”, and it’s “value” is the array elements themselves. A pointer to the array would be of type “int *”, and its value would be the address of the first element of the array.
In these definitions :
| declaration | name | [1] | sizeof() | arr/parr | &arr/&parr |
|---|---|---|---|---|---|
| int arr[10] | static array | 2 | 40 | ptr arr[0] | ptr arr[0] |
| int *parr | pointer to int | 2 | 8 | ptr arr[0] | ptr ptr to arr[0] |
So the type information int[10] gets lost (decay) when assigning parr.
Warning
in function calls int x[] or will be converted to int *x
Important
Use f(int *arr) instead of f(int arr[]) because it will be turned into the first form anyway.
Important
arrays inside structs and classes do NOT decay !
So if you really want functions to have the array information intact you can pass a struct containing the array :
| maintain array information | |
|---|---|
Otherwise you would have to pass the length as another parameter and do all checking manually.
example of array/ptr behavior
Here we alter a local parameter b in main just by calling function f() !. Note that this does not generate an error :
| array vs ptr | |
|---|---|
This will print:
| output | |
|---|---|
What happens is that this is the stackframe of main at the time of calling func
The stack frame for func will be
| explanation | |
|---|---|
At return it will be altered to :
| explanation | |
|---|---|
And b is just overwritten as 24, a number that happens to be aligned there.
pointer arithmetic
When adding integers to a pointer, the size of the pointer type including alignment is added. This process is called 'scaling'.
Important
The compiler actually translates arr[x] into *(arr + x)
So array indices always correspond to addition of the index to element 0.
strings
You can dynamically alter strings if created as a static array.
This prints Alyx, and it is like you create myname with calloc(5) and then filled it with "Alex0".
dynamic memory
chapter 14 exceptions
throw and catch work based on the type thrown. And you can throw any type of exception you want.
| exception | |
|---|---|
Here you would see try block and a - sign because -1 and 1 resolve to signed int, note that of course throw(1) and throw(1u) are never called.
Important
You can catch fundamental types by value, but for efficiency it is better to catch all other types by reference (move semantics).
Important
The compiler will not perform implicit conversion as in assignments.
So these throws are not caught, and the programs ends with the given error.
| exception | |
|---|---|
If an exception is thrown, nothing else is executed until the exception is handled. If the try block does not handle it or if it is outside of a try block the function is stopped and popped, and the same check is done one stack frame higher.
This process continues up to main, and is called stack-unwinding. If main also does not handle the exception main terminates with most OS's mentioning the unhandled exception but this is implementation dependent.
To prevent this you could at least provide a 'catch-all' handler in main.
| catch-all | |
|---|---|
exception specifiers
Warning
Only in C++11/14, not in later versions C++17 and up.
Functions may have this syntax in older C++ versions, specifying if they may, or will not throw exceptions :
| older c++ versions | |
|---|---|
As said, this is removed in C++17 but might be encountered in older code.
noexcept
This is the only specifier that survived in C++17 and up. It specifies that a function does NOT throw an exception. Destructors are always implicitly noexcept since they cannot throw exceptions.
Note that a noexcept function can still use exceptions internally and deeper, it will just not itself or let a subfunction throw any. So a catch-all block is probably of use in noexcept functions.
Important
noexcept is useful for making clear that a function does not throw, and for some compiler optimizations
constructors
If an exception occurs in a constructor, you can either handle it or throw an exception to the calling function. If you handle it the construction can just continue If you throw an exception all constructed members are destructed again, but the destructor is never called, because the object was never fully constructed.
exception classes
Exception types are not subject to implicit conversions, so a throw char will not be caught by catch(int).
If you would swap the catch clauses for Derived/Based, both would print base, because order matters !. However the g++ compiler will complain if you do so.
using the std::exceptions
Important
First of all, do not throw std::exception itself, because it is a base type for the others.
Some useful std exceptions are :
- invalid_argument(const std::string what)
- format_error() : not meant for json errors, but useful anyway
- runtime_error() : also a base class but fits better than some subclasses
- overflow_error()
- range_error():
see visit for more
you can always derive from std::exception (or std::runtime_error o.i.d) and make one yourself that fits perfect.
rethrowing exceptions
For instance if you just log an error but not handle it you can throw a new exception or even the same one. You are in a catch block, not a try block so it won't be caught again !!
However just rethrowing the exception object might slice it if it was caught as a base of an deeper class :
| re-throw | |
|---|---|
This can be avoided by not throwing an object (throw b;) but just using plain throw.
Function try blocks
These are needed if you want to handle an exception where you cannot reach the function, like in constructors :
| constructor | |
|---|---|
:A (x) calls the constructor for A but we have no room to put try linenums="1" catch() around it, so the exception will occur in function with new B() .
You can do this with an alternate syntax called function try block.
Note that the catch is actually outside of the constructor block {}
Warning
function try blocks must have an exception thrown up the stack again. If you don't the compiler will !
destructors
Important
Exceptions should NEVER be thrown in destructor because that entangles destruction and stack unwinding.
G++ will warn if you try :
Warning
warning: throw will always call terminate() [-Wterminate]
chapter C 5 control flow
Control flow statements with 1 line are actually converted to 1 line blocks. Note :
chained if statements
If you chain if else together it is like you nest them further each time
| else if | |
|---|---|
Is equivalent to :
| same as | |
|---|---|
Which should be indented as :
| indented | |
|---|---|
dangling else
Important
the 'dangling' else always belongs to the last unmatched if statement in the same block
init statement
New in stdc++17 is the init statement before the condition.
The variable will only be usable in the if statement and it's bodies (if and(or) else)
switch
Note that a switch is a compound statement, but a case is NOT.
Warning
case statements are NOT implicit block statements !!!
Important
You can declare but NOT initialize variables inside a switch. You will get these jump crosses initialization errors.
Jumping cross an initialization leaves that variable in an undefined state, it really skips all the statements in between and does not stop to handle the initialization ! So it is forbidden.
So .. :
chapter 15 smart pointers
example
enable_shared_from_this
If you use smart pointers you can assign these almost anywhere, as long as you match up the type. However, you have no vote in the type of 'this' inside the class. For instance
| this type | |
|---|---|
This can be solved by two steps : subclass Vehicle from std::enable_shared_from_this and calling the member function shared_from_this().