- Compilers:
- Sources:
- What is the
main
function? - the entry point of any C++ program - What arguments does it take? - argc and argv
- What are params for such case
./app --config /path/to/the/file
? - Can we overload it? - no
- How to access C functions from C++?
extern "C" void foo();
exit(int)
- normal; ~global, ~static, !~localabort()
- abnormal; !~dtorsstd::terminate()
- invokesabourt
by default
#include <...>
- includes other source file into current source file at the line immediately after the directive#include "..."
vs#include <...>
#pragma once
- avoid double inclusion##
macros concatenation
- What is the goal? - organize code into logical group, prevent name collision
- Nested? Unnamed? Alias?
template<class T>
void foo(ParamType p)
{
}
foo(expr);
- If
ParamType
is ref (non-forwarding) or ptr
- if
expr
is ref then ignore this ref part - then pattern-match the type of
ParamType
againstexpr
to determineT
template<class T>
void foo(T& p);
int x = 1; // foo<int>(int&)
const int cx = 2; // foo<int const>(int const&)
const int& rx = x; // foo<int const>(int const&)
const int a[] = { 1, 2, 3}; // foo<int const [3]>(int const (&) [3])
void f(int); // foo<void (int)>(void (&)(int))
template<class T>
void foo(const T& p);
int x = 1; // foo<int>(int const&)
const int cx = 2; // foo<int>(int const&)
const int& rx = x; // foo<int>(int const&)
const int a[] = { 1, 2, 3}; // foo<int [3]>(int const (&) [3])
void f(int); // foo<void (int)>(void ( const&)(int))
T
is never deduced to be a ptr or ref type
- If
ParamType
is a forwarding ref
- if
expr
is an lvalue ref thenT
andParamType
are lvalue - if
expr
is an rvalue then ref (non-forwarding) or ptr apply (see 1 item)
template<class T>
void foo(T&& p);
int x = 1; // foo<int&>(int&)
const int cx = 2; // foo<int const&>(int const&)
const int& rx = x; // foo<int const&>(int const&)
const int a[] = { 1, 2 }; // foo<int const*>(int const*)
void f(int); // foo<void (&)(int)>(void (&)(int))
foo(std::move(x)); // foo<int>(int&&)
- If
ParamType
is neither ref nor ptr
- if
expr
is a ref then ignore the ref part - then ignore const and volatile
template<class T>
void foo(T p);
int x = 1; // foo<int>(int)
const int cx = 2; // foo<int>(int)
const int& rx = x; // foo<int>(int)
const int a[] = { 1, 2 }; // foo<int const*>(int const*)
void f(int); // foo<void (*)(int)>(void (*)(int))
- What are pros and cons of using
auto
?
- makes code cleaner
- less error-prone
- does not imply use any temp var during initialization
- it is not possible to create uninitialized
auto
var - easiee to maintain
- Do you see a problem?
std::unordered_map<std::string, int> m;
// ...
for (const std::pair<std::string, int>& item : m) {
// ...
}
int main()
{
auto a = 42; // int
auto a1(42); // int
auto a2{42}; // int
//auto a3{ 42, 24 }; // error
auto a4 = { 42 }; // std::initializer_list<int>
}
auto
type deduction
int getInt();
int& getIntRef();
const int getConstInt();
const int& getConstIntRef();
int main()
{
auto ri = getInt(); // int
auto ri1 = getIntRef(); // int
decltype(auto) ri1_1 = getIntRef(); // int&
auto ri2 = getConstInt(); // int
auto ri3 = getConstIntRef(); // int
decltype(auto) ri3_1 = getConstIntRef(); // const int&
//auto& ri4 = getInt(); // error
auto& ri5 = getIntRef(); // int&
//auto& ri6 = getConstInt(); // error
auto& ri7 = getConstIntRef(); // const int&
auto&& ri8 = getInt(); // int&&
auto&& ri9 = getIntRef(); // int&
auto&& ri10 = getConstInt(); // int&&
auto&& ri11 = getConstIntRef(); // const int&
}
int i = 3;
decltype(i) ri14; // int
decltype((i)) ri15; // int&
decltype(auto) fn1() { int a = 0; return a; } // int
decltype(auto) fn2() { int a = 0; return (a); } // int&
-
Every expression is either an lvalue or an rvalue, but not both
-
lvalue
- a name or expression that referes to an object. Generally, an lvalue is a name for which the address may be taken -
rvalue
- a "value that is not an lvalue", often a temporary object created as an artefact of an expression or returned from a function. Can be moved/copied. It is not possible to take the address of an rvalue -
glvalue
(generalised lvalue) - is an lvalue that has identity or expression is either lvalue or xvalue -
xvalue
(extraordinary) - has identity and can be moved -
prvalue
(pure rvalue) - has no identity but can be moved
int& x = 666; // Error
// You are allowed to take the address of an rvalue
// only if you store it in a const (immutable) variable.
const int& x = 666; // OK
int x = 10;
//int&& xr = x; // error: rvalue reference to type 'int' cannot bind to lvalue of type 'int'
int&& xr1 = static_cast<int&&>(x); // int&&
decltype(auto) xr2 = x; // int
decltype(x) xr3 = x; // int
decltype((x)) xr4 = x; // int&
auto&& al2 = x; // int&
-
Automatic
(applicable only in function call) - initialised on each encounter, usually stack based, exist from definition to end of scope. -
Static
- initialised once only at definition and exists until end of a program execution
Automatic
and Static
are traditionally referred to as storage classes
-
Free store
- exist from the time of an explicit new operation until destroyed with delete -
Temporary object
- intermediate result in computation, or an object for a const reference, the lifetime depends on context, these are almost always stack-based objects -
thread-local object
- an object declared as thread_local, lifetime is that of the host thread
-
extern
- indicates that var is defined elsewhere (possibly in some other compilation unit), and this only a declaration -
static
-
register
- hint to compiler to give speed priority to variable, since C++11 is deprecated -
volatile
- all reads/writes from/to var shall be honoured (not optimised away)
The purpose of volatile is as an instruction to the compiler not to optimise-away what may seem like redundant read/write operations
-
automatic
-
global
-
thread_local
- indicates that each thread is allocated its own copy
int main()
{
const int a = 42;
[a]() { // -> [a = a]() mutable {
std::cout << ++a << '\n';
}();
return 0;
}
#include <iostream>
struct Foo {
Foo() { std::cout << "Foo()\n"; }
Foo(Foo&&) { std::cout << "Foo(Foo&&)\n"; }
Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; }
};
int main() {
Foo f; // Foo()
auto a = [f = std::move(f)]() { // Foo(Foo&&)
return std::move(f);
};
Foo f2(a()); // Foo(const Foo&)
// If a is mutable then Foo(Foo&&)
return 0;
}
std::any
can hold anything in a type-safe way
- std::any is not a template class
- std::any uses Small Buffer Optimization, so it will not dynamically allocate memory for simple types like ints, doubles… but for larger types it will use extra new.
- std::any might be considered ‘heavy’, but offers a lot of flexibility and type-safety.
- you can access the currently stored value by using any_cast that offers a few “modes”: for example it might throw an exception or just return nullptr.
- use it when you don’t know the possible types, in other cases consider std::variant.
int main()
{
auto a = std::any(12);
// set any value:
a = std::string("Hello!");
a = 16;
// reading a value:
// we can read it as int
std::cout << std::any_cast<int>(a) << '\n';
// but not as string:
try
{
std::cout << std::any_cast<std::string>(a) << '\n';
}
catch(const std::bad_any_cast& e)
{
std::cout << e.what() << '\n';
}
// reset and check if it contains any value:
a.reset();
if (!a.has_value())
{
std::cout << "a is empty!" << "\n";
}
// you can use it in a container:
std::map<std::string, std::any> m;
m["integer"] = 10;
m["string"] = std::string("Hello World");
m["float"] = 1.0f;
for (auto &[key, val] : m)
{
if (val.type() == typeid(int))
std::cout << "int: " << std::any_cast<int>(val) << "\n";
else if (val.type() == typeid(std::string))
std::cout << "string: " << std::any_cast<std::string>(val) << "\n";
else if (val.type() == typeid(float))
std::cout << "float: " << std::any_cast<float>(val) << "\n";
}
}
enum Alert { green, yellow, election, red };
enum class Color { red, blue };
enum class Color_code : char;
void foobar(Color_code* p);
enum class Color_code : char { red, yellow, green, blue };
int main()
{
Alert a = 7; // error: invalid conversion from 'int' to 'Alert' [-fpermissive]
Color c = 7; // error: cannot convert 'int' to 'Color' in initialization
int a2 = red;
int a3 = Alert::red;
int a4 = blue; // error: 'blue' was not declared in this scope
int a5 = Color::blue; // error: cannot convert 'Color' to 'int' in initialization
Color a6 = Color::blue;
}
#include <iostream>
struct Foo {
Foo() { std::cout << "Foo()\n"; }
Foo(Foo&&) noexcept { std::cout << "Foo(Foo&&)\n"; }
Foo& operator=(Foo&&) noexcept { std::cout << "operator=(Foo&&)\n"; }
Foo(const Foo&) { std::cout << "Foo(const Foo&)\n"; }
Foo& operator=(const Foo&) { std::cout << "operator=(const Foo&)\n"; }
};
int main()
{
Foo foo1; // Foo()
Foo foo2{}; // Foo()
Foo foo3 = Foo{}; // Foo()
Foo foo4 = {}; // Foo()
Foo foo5{ foo4 }; // Foo(const Foo&)
Foo foo6 = foo5; // Foo(const Foo&)
Foo foo7; // Foo()
foo7 = foo6; // operator=(const Foo&)
Foo foo8 = std::move(foo7); // Foo(Foo&&)
Foo foo9; // Foo()
foo9 = std::move(foo8); // operator=(Foo&&)
return 0;
}
- принцип единственной ответственности/обязанности (single responsibility) - объект должен иметь одну обязанность; одну причину для исправления; отвечать за одного и только за одного актора
- принцип открытости/закрытости (open/close) - программные сущности (функции, классы, модули и т.д.) должны быть открыты для расширения, но закрыты для изменения
- принцип подстановки Лисков (Liskov substitution) - функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа не зная об этом
- принцип разделения интерфейсов (insterface segregation) - клиенты не должны зависеть от методов которые они не используют; слишком "толстые" интерфейсы необходимо разделять на более мелкие и специфические
- принцип инверсии зависимостей (dependency inversion) - модули должны зависеть об абстракций; абстракции не должны зависеть от деталей, а вот детали должны зависить от абстракции
- Where do you see a problem? How to fix it?
int main(int, const char* [])
{
const int a = 2; // 1
int h(5); // 2
const int* i = &h; // 3
int* const k = &a; // 4 error: invalid conversion from 'const int*' to 'int*'
int const* j = &a; // 5
int* l(); // 6
*i = 4; // 7 error: assignment of read-only location '* i'
++i; // 8
++j; // 9
++k; // 10 error: increment of read-only variable 'k'
++*k; // 11
++l; // 12 warning: ISO C++ forbids incrementing a pointer of type 'int* (*)()'
return 0;
}
- What methods can be callable for b and f pointers? Why?
class Foo
{
public:
void Fn() {};
void Fn(char*) {};
};
class Bar : public Foo
{
public:
void Fn(int) {};
};
int main(int, const char* [])
{
Bar* b = new Bar;
//b->Fn(); // error: no matching function for call to 'Bar::Fn()'
// b->Fn("123"); // error: invalid conversion from 'const char*' to 'int'
b->Fn(123);
Foo* f = b;
f->Fn();
f->Fn("1");
return 0;
}
- What is the output result? If you find a mistake, please fix it.
class A
{
public:
A() { std::cout << "A"; }
~A() { std::cout << "a"; }
};
class B : public A
{
public:
B() { std::cout << "B"; }
~B() { std::cout << "b"; }
};
int main(int, const char* [])
{
B b; // 1
std::unique_ptr<A> a = std::make_unique<B>(); // 2
return 0;
}
// ABABaba
- What is the output of the following program? Please explain it.
template <typename T>
class Counter
{
public:
Counter()
{
std::cout << ++x;
}
private:
static int x;
};
template <typename T> int Counter<T>::x = 0;
int main(int, const char* [])
{
Counter<int> a;
Counter<int> b;
Counter<double> c;
Counter<Counter<double>> d;
return 0;
}
// 1211
- What is the result of the following program?
class A
{
public:
A() { p(); }
void p() { func(); }
virtual void func() = 0;
};
class B : public A
{
public:
virtual void func() { std::cout << "B::func"; }
};
int main(int, const char* [])
{
B b;
b.func();
A* a = new A(); // error: invalid new-expression of abstract class type ‘A’
return 0;
}