This document captures basic concepts and features used in modern C++. I tend to learn better by example, so I will briefly describe and concept/feature and then give example(s) to show how it is used.
Note: This document is in progress.
These notes will be in literate programming style. All code will be compiled and executed using the clang compiler. Specifically, this command was used. In some examples, I also plan to do benchmarking using this library.
- Scott Meyer’s excellent books on C++ practices: https://www.aristeia.com/books.html
- Andrei Alexandrescu’s Modern C++ design: https://erdani.com/index.php/books/modern-c-design/
- Bo’s C++ videos on youtube
- Stackoverflow, Quora, cpp reference
Zoomed out view of the language concepts: https://en.cppreference.com/w/cpp/language/basic_concepts
Quote:
” A C++ program is a sequence of text files (typically header and source files) that contain declarations. They undergo translation to become an executable program, which is executed when the C++ implementation calls its main function.
Certain words in a C++ program have special meaning, and these are known as keywords. Others can be used as identifiers. Comments are ignored during translation. Certain characters in the program have to be represented with escape sequences.
The entities of a C++ program are values, objects, references, structured bindings (since C++17), functions, enumerators, types, class members, templates, template specializations, namespaces, and parameter packs. Preprocessor macros are not C++ entities.
Declarations may introduce entities, associate them with names and define their properties. The declarations that define all properties required to use an entity are definitions. A program must contain only one definition of any non-inline function or variable that is odr-used.
Definitions of functions usually include sequences of statements, some of which include expressions, which specify the computations to be performed by the program.
Names encountered in a program are associated with the declarations that introduced them using name lookup. Each name is only valid within a part of the program called its scope. Some names have linkage which makes them refer to the same entities when they appear in different scopes or translation units.
Each object, reference, function, expression in C++ is associated with a type, which may be fundamental, compound, or user-defined, complete or incomplete, etc.
Declared objects and declared references that are not non-static data members are variables. ”
Each object has a life cycle.
We’ll dig deeper into some of these below.
See: https://en.cppreference.com/w/cpp/language/type
“Objects, references, functions including function template specializations, and expressions have a property called type, which both restricts the operations that are permitted for those entities and provides semantic meaning to the otherwise generic sequences of bits.”
Aka fundamental or built-in. https://en.cppreference.com/w/cpp/language/type.
https://github.com/spraza/tut-notes/blob/master/cpp-stl.org
First, we need to understand the difference between lvalues and rvalues:
- https://docs.microsoft.com/en-us/cpp/cpp/lvalues-and-rvalues-visual-cpp?view=msvc-160
- https://youtu.be/UTUdhjzws5g
std::move doc: https://en.cppreference.com/w/cpp/utility/move. Basically, std::move(X) is exactly equivalent to static_cast<T&&>(X) where T is the deduced type of expression X. T&& is the rvalue reference type.
rvalue reference generally means “no address”. so when a function or template parameter is typed as rvalue reference, then we know that the thing being passed can not be accessed outside – so i am the only one who knows about it and so i can optimize my code based on it. one example: reuse data in rvalue reference instead of making another copy (cz then we’ll have 2 copies and the one passed in will be wasted later).
Let’s see how using move and rvalue reference to do shallow copies instead of deep copy helps:
#include <iostream>
#include <memory>
#include <chrono>
#include <type_traits>
using namespace std;
template<class T> class MyVector {
public:
MyVector() : ptr(new T[cap]) { // ctor
cout << "ctor" << endl;
init(ptr);
}
MyVector(const MyVector& rhs) { // copy ctor
cout << "copy ctor" << endl;
sz = rhs.sz;
ptr.reset(new T[cap]);
init(ptr);
}
MyVector(MyVector&& rhs) { // move ctor
cout << "move ctor" << endl;
sz = rhs.sz;
ptr = move(rhs.ptr);
}
T operator[](size_t i) const {
return ptr[i];
}
private:
const size_t cap = 100 * (1 << 20); // 100M elements capacity
size_t sz = 0;
unique_ptr<T[]> ptr;
void init(const unique_ptr<T[]>& ptr) {
for (size_t i = 0; i < cap; ++i) {
ptr[i] = 456; // some initial value
}
}
};
template <typename F>
static chrono::milliseconds time(F&& f) {
using chrono::high_resolution_clock;
using chrono::milliseconds;
using chrono::duration_cast;
auto t1 = high_resolution_clock::now();
f();
auto t2 = high_resolution_clock::now();
return duration_cast<milliseconds>(t2 - t1);
}
static void ex1() {
cout << "*** ex1 ***" << endl;
auto f1 = []() { MyVector<int> v; };
cout << time(f1).count() << "ms\n" << endl;;
auto f2 = []() {
MyVector<int> v;
MyVector<int> v1(v);
};
cout << time(f2).count() << "ms\n" << endl;
auto f3 = []() {
MyVector<int> v;
MyVector<int> v1(move(v));
};
cout << time(f3).count() << "ms\n" << endl;
}
MyVector<int> create() {
return MyVector<int>();
}
void doSomething(MyVector<int> v) {
return;
}
static void ex2() {
cout << "*** ex2 ***" << endl;
auto f1 = []() {
auto v = create();
doSomething(v);
};
cout << time(f1).count() << "ms\n" << endl;;
auto f2 = []() {
// cout << "create() gives a rvalue reference? "
// << is_compound<decltype(create())>::value
// << endl;
doSomething(create());
};
cout << time(f2).count() << "ms\n" << endl;
auto f3 = []() {
// cout << "create() gives a rvalue reference? "
// << is_rvalue_reference<decltype(move(create()))>::value
// << endl;
doSomething(move(create()));
};
cout << time(f3).count() << "ms\n" << endl;
return;
}
int main() {
//ex1();
ex2();
return 0;
}| *** | ex2 | *** |
| ctor | ||
| copy | ctor | |
| 1732ms | ||
| ctor | ||
| 881ms | ||
| ctor | ||
| move | ctor | |
| 868ms |
Intrestingly, for things like doSomething(create()), you may expect the move ctor to be invoked but it’s not. Recommended reading: https://en.cppreference.com/w/cpp/language/copy_elision. -fno-elide-constructors compiler flag can disable this compiler optimization in most compilers. It is also possible that some compilers don’t optimize this, so this is very compiler specific.
Also cover static vs dynamic typing and tradeoffs.
https://en.cppreference.com/w/cpp/language/template_parameters
Templates are recipes, not actual classes. That’s why they need to be present in the header files and not source files.
- Metaprogramming
- Concurrency. Also covered in https://github.com/spraza/tut-notes/blob/master/concurrent-programming-cpp.org.
- Lambdas
- Smart pointers
Some of these will be covered in C++11 features section above.