It's months I'm far from programming wholly but already have a list of notes to refresh my mind about programming in occasions like this. So, I will list half of those notes in this post I've previously written about C++ and hopefully you do me a favour to assign some time to check them. If any item is wrong or written wrongly or there's an update regarding that, please let me know.
Thanks beforehand.
1) A constexpr symbolic constant must be given a value that is known at compile time whereas a const works for both compile and run times.
const: meaning roughly “I promise not to change this value.” This is used primarily to specify interfaces so that data can be passed to functions using pointers and references without fear of it being modified. The compiler enforces the promise made by const. The value of a const can be calculated at run time.
constexpr: meaning roughly “to be evaluated at compile time.” This is used primarily to specify constants, to allow placement of data in read-only memory (where it is unlikely to be corrupted), and for performance. The value of a constexpr must be calculated by the compiler.
A constexpr function can be used for non-constant arguments, but when it's used this way, the result is not a constant expression. We allow a constexpr function to be called with non-const-expression arguments in contexts that do not require constant expressions. That way, we don't have to define essentially the same functions twice: once for constant expressions and once for variables. If a function may have to be evaluated at compile time, declare it constexpr.
The body of constexpr functions is evaluated at compile time only if the result is used in a const/constexpr expression and constexpr can be used with constructors and objects. The arguments and the return type, that way, must have literal values only.
A constant expression is an expression which can be evaluated at compile time and it's marked by either the const or constexpr keyword.
2) One particularly useful kind of template is the function object (sometimes called
a functor), which is used to define objects that can be called like functions. The function called operator() implements the “function call,” “call,” or “application” operator (). Such function objects are widely used as arguments to algorithms. A predicate is something that we can invoke to return true or false. The beauty of these function objects is that they carry the value to be compared against with them. We don’t have to write a separate function for each value (and each type), and we don’t have to introduce nasty global variables to hold values. The ability to carry data plus their efficiency makes function objects particularly useful as arguments to algorithms.
3) The type of a C-style string literal (“…”) is const char*. If that was not what was intended, use an s suffix to make it a proper string.
4) C++ enum, on the one hand, is a container for constant items carrying int values starting from 0 by default which can be altered, at will. So, if we have a set of values we want to represent numerically, the best way probably is an enum. And the name of the enum thereafter is used as a type. Enum classes, on the other hand, are useful to prevent confliction with two const values having the same name in two enum sets, for instance. In those classes, we need to use the scope resolution operator :: to pinpoint the item in the exact class.
5) Resource Acquisition Is Initialization (RAII) is a C++ technique where you wrap a class around a resource (file, socket, database connection, allocated memory, ...). The resource is initialized in the class constructor and cleaned up in the class destructor. This way you are sure to avoid resource leaks.
6) Mark your move constructors and move assignment operators with noexcept because nothing is normally going to throw.
7) Define inline in header files. Functions defined in a class are inlined by default
8) For many uses, a variant is simpler and safer to use than a union.
9) Prefer class enums (also called a scoped enumeration) over “plain” enums to minimize surprises
10) In type const * and const type *, the content of the pointer is constant, whilst in type* const, it’s the address of that pointer which is constant.
11) A mutable var allows constant member functions (promise not to change anything in the class) to change it.
12) Each .cpp file is called a translation unit essentially because they’re to be translated/converted to the machine language, .obj files, by the compiler. The job of the Linker, on the other hand, is to link those object files (.obj) together to generate the .exe file.
13) Throw an exception to indicate that you cannot perform an assigned task and don’t try to catch every exception in every function. Often, a function has no way of completing its assigned task after an exception is thrown. Then, “handling” an exception means doing some minimal local cleanup and rethrowing the exception.
14) The static_assert mechanism can be used for anything that can be expressed in terms of constant expressions. In general, static_assert(A,S) prints S as a compiler error message if A is not true. The most important uses of static_assert come when we make assertions about types used as parameters in generic programming.
15) When it comes to conversions, more types are like std::vector than are like complex,
so use explicit for constructors that take a single argument unless there is a good reason not to.
16) We refer to traditional C++ references (the single-ampersand one) as lvalue references. Modern C++ has introduced a new type called rvalue reference, denoted by placing a double ampersand && after some type. Such rvalue reference lets you modify the value of a temporary object. Rvalue references pave the way for the implementation of move semantics, a technique which can significantly increase the performance of your applications.
real quick pass so maybe not 100% of it but...
7) inline is a suggestion to the compiler. Modern compilers inline on their own very nicely, and use of the keyword is often not necessary. There are other ways to enforce inline if the compiler isnt doing what you want, but its very rare to need that.
8) neither of these are used a lot in c++.
9) True. The C style enum is less type safe, since they boil back down to integers. This is both an advantage and a disadvantage. The inability to the enum class to be treated as raw integers without a cast is a large hassle though. If you need to treat them as raw integers (which is 99% of what I use them for!), the old enum can be used in a namespace if you want to protect it from collisions rather than cast every single use of the enum everywhere. Maybe some of the pros can weigh in on this, but the new enums seem to be trying to protect you from doing what the enum exists to do in the first place.
> The body of constexpr functions is evaluated at compile time only if the result is used in a const/constexpr expression
An implementation is allowed to evaluate the result of invocation of any function at compile-time if it does not alter the observable behaviour of the program. The as-if rule: https://en.cppreference.com/w/cpp/language/as_if
For instance, the as-if rule allows the compiler to rewrite function bar() below as int bar() { return 16 ; }
The result of the call foo( x, &y ) below and that of a call to bar() with any argument at all
may be evaluated at compile-time.
1 2 3 4 5 6 7 8 9 10 11 12
int foo( int a, constint* b ) { return a + *b ; }
int bar( int arg )
{
int x = arg + 2 ;
int y = 16 ;
x += 5 ;
y -= 7 ;
if( y%7 == 3 ) y *= x ;
else y -= arg ;
return foo( x, &y ) ;
}
The following are the rest of the rules. If possible please have a look at them too.
17) Copy constructor/assignment is called when there’s an lvalue in input, while move constructor/assignment is called when there’s an rvalue in input.
18) With the utility function std::move from the Standard Library we can move lvalues. It is used to convert an lvalue into an rvalue. std::move will convert its lvalue argument into an rvalue to steal data from it by the move semantics.
19) The standard-library containers and std::string all have fast move operations.
20) When declaring a class, it normally must have 7 member functions:
Default constructor
Normal constructor (with arguments for instance)
Copy constructor
Copy assignment
Move constructor
Move assignment
Destructor
If you supply a custom constructor, the compiler doesn’t generate a default constructor!
We use = default; to make the compiler create a default one for that method. A good rule of thumb (sometimes called the rule of zero) is to either define all of the essential operations or none (using the default for all).
21) size_t is the name of the type returned by a standard-library size()
22) For some sophisticated concrete types, such as vector, memberwise copy (copy each member) is not the right semantics for copy; for abstract types it almost never is.
23) C++ polymorphism is mainly divided into two types: compile time and run time polymorphism with the former occurring in operators and functions in or outside classes while the latter occurring while a base class virtual function is called differently by different calls from derived class’s instances.
24) Templates and function overloading are the use of Generic Programming.
25) Code is cleaner when dynamic_cast is used with restraint. If we can avoid using type
information, we can write simpler and more efficient code, but occasionally type information is lost and must be recovered. This typically happens when we pass an object to some system that accepts an interface specified by a base class. When that system later passes the object back to us, we might have to recover the original type. Operations similar to dynamic_cast are known as “is kind of” and “is instance of” operations.
26) Use dynamic_cast where class hierarchy navigation is unavoidable. Use dynamic_cast to a reference type when failure to find the required class is considered a failure. Use dynamic_cast to a pointer type when failure to find the required class is considered a valid alternative.
27) A unique-pointer is a scoped pointer, meaning, when that pointer goes out of scope, it will get destroyed and call delete. They’re unique-pointers, so it’s not possible to copy them. If we want to share a pointer (pass it to a function or another class), we use shared-pointers. The way a shared-pointer works is via reference counting: it keeps track of how many references there are for that pointer. As soon as that reference count reaches zero, it’s when it gets deleted.
28) A dangling pointer points to memory that has already been freed. For instance, when a pointer goes out of scope, the address allocated for that in the scope is deallocated. Hence, it’s mostly not possible to access that location by the returned pointer.
A memory leak is memory which hasn’t been freed and there’s no way to access or free it as there’s no way to get to it, say, the pointer previously pointing to that is now pointing to somewhere else in the memory.
29) A pure virtual function of a base class (abstract class) must be implemented in a subclass before we can instantiate that subclass.
30) A class with a virtual function should have a virtual destructor.
31) Static elements are storage allocated only once in the static storage area, not stack or heap and hold that storage throughout the whole program lifetime. They’re initialized only once: static variables inside functions & static classes objects.
Primitive data types are by default zero-initialized. Static data members in classes have shared storage by all objects not a separate copy for each object. They must be initialized ‘outside’ of the class, explicitly. Similar to these are static member functions that can be called by an object and also direct member access operator ‘.’, but more typically by class name and the scope resolution operator ::. These functions don’t have the this keyword, thus they cannot access non-static member functions and data members. The static keyword for a global function or variable limits the visibility and linkage scope of the function/variable to the current translation unit only.
32) A lambda expression is an unnamed function defined right where it’s needed as an argument. The [ ] is called the lambda introducer and it’s used to get variables outside of its body or arguments list. And then the list of required arguments (the arguments list) and the actions to be performed (function body). The return type is rarely needed and can be deduced from the lambda body. A lambda can introduce new variables in its body (in C++14).
You can use the default capture mode (capture-default in the Standard syntax) to indicate how to capture any outside variables that are referenced in the lambda: An empty lambda introducer, [ ], means “no capture is made”, [&] means all variables that you refer to are captured by reference, and [=] means they are captured by value. You can use a default capture mode, and then specify the opposite mode explicitly for specific variables. Only variables that are mentioned in the lambda are captured when a capture-default is used.
To use lambda expressions in the body of a class method, pass the this pointer to the capture clause to provide access to the methods and data members of the enclosing class.
28) A dangling pointer points to memory that has already been freed. For instance, when a pointer goes out of scope, the address allocated for that in the scope is deallocated.
Your first sentence is correct, but I don't see how the second sentence relates to the first one. Your second sentence is an example of a possible memory leak, which you also mention below.
> 17) Copy constructor/assignment is called when there’s an lvalue in input,
> while move constructor/assignment is called when there’s an rvalue in input.
With rvalues, move constructor/assignment is invoked if the move constructor/assignment is present (not deleted). Otherwise, a copy constructor/assignment (const T&) would invoked.
> size_t is the name of the type returned by a standard-library size()
std::size_t: https://en.cppreference.com/w/cpp/types/size_t
"When indexing C++ containers, such as std::string, std::vector, etc, the appropriate type is the member typedef size_type provided by such containers. It is usually defined as a synonym for std::size_t."
> A class with a virtual function should have a virtual destructor.
Probably if we add this to the text the explanation will be right: If such a pointer is used to be copied to another pointer, that new pointer will be a dangling pointer, because it points to somewhere in the memory that doesn't exist anymore (it has been deallocated).
Thank you guys all very much.
My last question is, is there any rule commonly used in C++ (and probably is modern) that I've forgotten to mention in the list above, please?