Skip to content

Instantly share code, notes, and snippets.

@SlavaMelanko
Last active November 1, 2018 21:12
Show Gist options
  • Save SlavaMelanko/380c8d711e42e08b1a4f1441deed1f67 to your computer and use it in GitHub Desktop.
Save SlavaMelanko/380c8d711e42e08b1a4f1441deed1f67 to your computer and use it in GitHub Desktop.

Slava Melanko Quiz

  1. Compilers:
  1. Sources:

Table of Contents

Main Function

  1. What is the main function? - the entry point of any C++ program
  2. What arguments does it take? - argc and argv
  3. What are params for such case ./app --config /path/to/the/file?
  4. Can we overload it? - no

C

  1. How to access C functions from C++?
extern "C" void foo();

Program Termination

  1. exit(int) - normal; ~global, ~static, !~local
  2. abort() - abnormal; !~dtors
  3. std::terminate() - invokes abourt by default

Pre-Processor

  1. #include <...> - includes other source file into current source file at the line immediately after the directive
  2. #include "..." vs #include <...>
  3. #pragma once - avoid double inclusion
  4. ## macros concatenation

Namespace

  1. What is the goal? - organize code into logical group, prevent name collision
  2. Nested? Unnamed? Alias?

Type Deduction

template<class T>
void foo(ParamType p)
{
}

foo(expr);
  1. If ParamType is ref (non-forwarding) or ptr
  • if expr is ref then ignore this ref part
  • then pattern-match the type of ParamType against expr to determine T
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

  1. If ParamType is a forwarding ref
  • if expr is an lvalue ref then T and ParamType 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&&)
  1. 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))

Auto

  1. 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
  1. 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>
}
  1. 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&
}

decltype

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&

rvalue and lvalue

rvalue-and-lvalue-img-0

  1. Every expression is either an lvalue or an rvalue, but not both

  2. lvalue - a name or expression that referes to an object. Generally, an lvalue is a name for which the address may be taken

  3. 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

  4. glvalue (generalised lvalue) - is an lvalue that has identity or expression is either lvalue or xvalue

  5. xvalue (extraordinary) - has identity and can be moved

  6. prvalue (pure rvalue) - has no identity but can be moved

rvalue-and-lvalue-img-1

rvalue-and-lvalue-img-2

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&

Object Lifetime

  • 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

Storage Classes

  • 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

Lambda

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

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 class

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;   
}

Class

#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;
}

SOLID

  • принцип единственной ответственности/обязанности (single responsibility) - объект должен иметь одну обязанность; одну причину для исправления; отвечать за одного и только за одного актора
  • принцип открытости/закрытости (open/close) - программные сущности (функции, классы, модули и т.д.) должны быть открыты для расширения, но закрыты для изменения
  • принцип подстановки Лисков (Liskov substitution) - функции, которые используют базовый тип, должны иметь возможность использовать подтипы базового типа не зная об этом
  • принцип разделения интерфейсов (insterface segregation) - клиенты не должны зависеть от методов которые они не используют; слишком "толстые" интерфейсы необходимо разделять на более мелкие и специфические
  • принцип инверсии зависимостей (dependency inversion) - модули должны зависеть об абстракций; абстракции не должны зависеть от деталей, а вот детали должны зависить от абстракции

Practive

  1. 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;
}
  1. 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;
}
  1. 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
  1. 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
  1. 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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment