C++
We are playing with data.
This guide is based on a Youtube serie about C++ made by TheChernoProject.
cplusplus.com cppreference.com
- Useful
- Compiler&Linker
- how_it_works
- Variables
- Functions
- Header_files
- Conditions_and_branches
- Loops
- Control_Flow
- Pointers
- References
- Classes
- Class vs Structures
- Const
- How_to_write_a_class
- Static_Cpp_Outside Class
- Local_Static
- ENUMS
- Constructor
- Destructor
- Inheritence
- Virtual Fuctions
- Interfaces_Pure_Virtual_Functions
- Visibility
- Arrays
- Strings_in_cpp
- String_Literals_in_Cpp
- Mutable_keyword
- Constructor_Initializer_List
- Ternary_Operators
- Create_Instantiate_objects
- New_keyword
- Implicit_conversion_Explicit_keyword
- Operators_and_Operator_Overloading
- This_keyword
- Object_Lifetime
- Smart_Pointers
- Copying_and_Copy_Constructors
- Arrow_Operator
- Dynamic_Arrays_std_vector
- Using_Libraries
- Multiple_return_values
- Templates
- Stack_vs_Heap_Memory
- Macros
- Auto_keyword
- Static_Arrays
- Function_Pointers
- Lambdas
- Namespaces
- Threads
- Multidimensional_Arrays_2D_Arrays
- Type Punning
- Unions
- Casting
- **#include **:library to use random number (can call rand())
- rand()%9 to call rand num between 1 and 9
- srand(): truly rand with a seed as arg
- **#include ** : to use time
- srand(time(0)): seed the time to rand
Error: Linking_error: Two symbols (fonction, class, variable) that have the same name.
- Compiler changer code to be understandable by machine
- Linker connect data
You can trick compiler by just giving him what he wants:
For example if the compiler wants a function we can just create a new file with this function, then include in our main file this file then call the function before main like that:
void Log(int p);
- Pre -processing :
#include ... basically copy header files before your program
**#include ** will make streaming fucntion like cout or cin available to use.
binaries, executable files
main function return int because return 0 if everything works
We can use Long and short variables.
We can use unsigned variables.
char can be number or caracter.
sizeof(bool) : give size of variable
int -> 4 bit
bool -> 1 bit
double -> 8 bit
Function with return type:
type name (parameters)(){
return type
}
Function with no return type:
(don't need return)
void name(parameters(){
}
parameters: what has changed between blocs of code, between different uses of the function
Common task multiple time -> create a function
We usually break up functions into Declaration and definition:
Declaration -> header files
Definition -> translation unit (.cpp files)
They are used to declare varibales or functions that are used in multiple programs. They are not used to make definition of the function but just to say that this function exist.
#include "name.h" ("" include file are relative to the current file and <> have to be in one of the compiling include path directories )
name.h
Exemple:
#pragma once // (declare only once,
// to not include twice the same thing) (better)
or can :
#ifndef _LOG_H
#define _LOG_H
#endif (same effect than pragma once)
void initLog();
void Log(const char* message);
An if conditions is jumping to some part of memory
if statements would be avoid if you want a super fast code
Exemple :
int x = 5;
bool comparisonResult = x ==5;
if (comparisonResult){
}
if the if statement is only one line of code you can not use {}
To check if a number/or a pointer is a valid value, we can use an if statement, if null will not go into the loop:
if (x) { // ==> equal to if (x! = null)
"hello"}
else if is equal to else{
if {
- For
for (int i = 0; i<5; i++){
}
bool condition = true;
for ( ; condition ; ){
}
for ( ; ; ){
} endless loop
- While
while ( i <5 ){
}
- DO
do{
}while (i<5) : will be done at least one time
Gives more control how loops run
*continue : inside loop, skip to the next iteration of this loop or end loop
for (int i=5; i<5; i++){
if (i %2 == 0)
continue;
}
break : inside loop or switch statement, get out of loop
return : get out of the function
Managing Memory
A memory in a computer is a long straight street with a start and end, and house along with numbers (bits in computers)
pointer : an integer that stores a memory address (address where the house is, where the specific bit of memory is)
void* : we don't care what type the pointer is
void* ptr = nullptr; // (memory address 0)
int var = 8;
void* ptr = &var; // store the memory address
// of var in ptr
Types doesn't matter for ptr, it matters for manipulation of memories, but types matters for ptr when you want to read from ptr
Read from pointer :
int var = 8; //(stored on the stack)
int* ptr = &var; //change the memory address of ptr to the one of var
*ptr = 10; // (access value of ptr, stored in a
// memory address)
Create a variable on the heap :
char* buffer = new char[8]; // (I want to allocate
//some memory for me, asking for 8 bits of memory)
memset( buffer, 0 , 8) // files 8 bit of memory
//set to 0 (filles a block of memory with data)
delete[] buffer; //( delete the memory used)
Pointers are variables :
double pointer, ptr to ptr , variable storing memory adress pointing to an other variable stroing an another address that as the data
(*ref) is called de-reference
char ptr** : a ptr to a ptr
int* m_X, *m_Y
An extension of ptr
pretty much the same things as ptrs
reference : a way to reference an existing variable, it's an alias !
int a =5;
int& ref = a; // ref = a so if we modifie ref we modifie a and if we modifie a we modifie ref
we created an allias, this variable doesn't realy exist for the computer, it's just the same variable all the time.
**Comparison **:
With Ptr :
void Increment (int* value){
(*value)++;
}
int main() {
int a =5;
Increment(&a);
}
With reference :
void Increment (int& value){ //It means : int& value = a; value has become a reference of a
value++;
}
int main() {
int a = 5;
Increment(a);
}
Synthax sugar, cleaner, simpler
You have to immediately initialize.
Once you declared a reference, you can not change what it's refering too
Always pass your object as const reference, at a basic level !!!
Object oriented programming
Class : put together data and fct
Class player{ (unique type, creating a new variable type)
public:
int x, y
int speed;
};
visibility : how visible is the stuff inside the class (default private)
public : can access to this variable outside this class
method : function of a class
Class player{
public:
int x, y
int speed;
void move (int xa, ya){
x += xa *speed;
y+=ya*speed;
}
Synthax sugar
They can't do anything than structures can't do.
A class is private by default.
A struct is public by default.
Struct exist in part to have compatibility with C.
struct player{
}
When do I use struct : usage: playing on data, structure that represente variable, like a vector class;
struct vec2{
float x,y;
void Add(const Vec2& other){
x = other.x +x;
y= other.y +y;
}
}
Representing data in a structure, use struct.
I would never use inheritence with struct.
const : variables declared with ‘const’ added become constants and cannot be altered by the program
- Simple use :
const int Constant1=96;
const int* Constant2 : variable ptr to a cte int , cannot modify the content of the ptr
int const* Constant2: same ( look the position of the ptr symbol*)
int* const Constant3: cte ptr to a variable int
int const * const Constant: cte cte
- Use of const in functions return :
The const pointer to a variable is useful for storage that can be changed in value but not in memory
A ptr to a const value is useful for returning const strings and array from fcts which because they are implemented as ptrs, the prog will try to alter and crash. But you will know the error that the value was not suppose to be altered.
- In parameter passing :
void Subroutine4(big_structure_type const &Parameter1);
which will cause the variable to be passed without copying but stop it from then being altered.
- Object oriented :
class Class2{
void Method1() const;
int MemberVariable1;
};
which will ban Method1 in Class2 from being anything which can attempt to alter any member variables in the object.
Example:
#include <iostream>
#include <string>
class Entity{
private:
int m_X, m_Y;
public:
int GetX() const // this method is not going to modify the class, any members variables, read only
{
return m_X;
}
//
const int* const GetX() const { //return a cte ptr that has a cte content that can not modifed the class
return m_X
}
void SetX(int x){ // not const, because we write to the class
m_X = x;
}
};
void PrintEntity(const Entity& e){
std::cout<< e.GetX()<< std::endl; //if I have a const ref in the argument, I can only call a const method.
}
see code LogClass.cpp in home/Dev
To be completed
- Static outside a class
static int s_Variable : this variable is going to be linked only internly in this translation unit. It's kind of saying the variable private to the linker.
It's only going to be visible inside this .cpp file and not another.
We can't have two global variable with the same name.
We can use: extern int s_Variable
When do I use static outside a class : if we don't need variable to be global, always being static unless we need cross linking between translation unit
- Static inside a class
They are like a namespace but in a class.
When do I have to use static inside a class : a data to be shared accross class, replace global variable. A data to be shared with all instances
It's like a global variable but where we know exactly where is it.
Organization sugar.
Static methods :
Have to use static arguments.
A static method doesn't have a class instence.
When do I use static method : Static data, data that doesnt change between class instence but we want actually to use inside our class
Two parameters to look for variables: Live time of a variable / scope of the variable (where the variable can be use)
a static Local variable : live time = the entire program / scop = limited inside this fct
void Funtion(){
static int i = 0 ;
i++;
std::cout <<i<<std::endl;
}
==
int i = 0 ;
void Funtion(){
i++;
std::cout <<i<<std::endl;
}
Example:
class Singleton{
private:
static Singleton* s_Instance;
public:
static Singleton& Get() { return *s_Instance;}
void Hello(){}
};
Singleton* Singleton::s_Instance=nullPtr;
int main(){
Singleton::Get().Hello();
}
==
class Singleton{
public:
static Singleton& Get() {
static Singleton instance;
return *s_Instance;
}
void Hello(){}
};
int main(){
Singleton::Get().Hello();
}
Enums : a way to give a name to a value
A way to name value
Helps to have a cleaner code
Example :
enum Example : unisgned char { //int by default
A = 0 ,B = 6 ,C = 8 //if not defined start from
// 0 then increment 1 (A=0 , B=1,...)
};
int main {
Example value = B;
if (value ==1){
}
}
Initialize a class.
A special type of method launched everytime we instanciated an object
Replace a void init()
A method that get called every time we construct an object
Class Entity{
public:
float X,Y
Entity() { // as to match the name of the class
}
Entity (float x, float y){
X=x;
Y=y;
}
};
Entity e(10.0f,5.0.f);
To delete the default constructor: Log() = delete;
A special method gets called when an object get destroyed (for example when we get out of a fct loop)
Unitialize everything, opposite of the constructor, free memory
~Entity() { // ~ to specify a destructor and not constructor
}
inheritence : allows base class contain basic functionnality then branch to have subclass from this parent class
class Entity{
public:
float X,Y;
void Move(float xa, float ya){
X=+ xa;
Y=+ya;
}
};
class Player : public Entity
{
public:
const char* name;
};
int main{
player.Move(5, 5);
}
Player is a sub class of entity. It has the type player and entity.
Playert has everything entity has.
We can add informations that are not in entity in player.
Polymorphise:
We can also use Player everywhere we wanted to use Entity, because Player has everything from Entity + some specific stuff.
Inheritence:
A way to extend an existing class, profide new functionnalities to a base class. Super class = Entity, base class
Virtual fuctions : overwrite methods in subclass
Class Entity{
public:
std::string getName() { return "Entity";}
};
Class Player: public Entity{
private:
std::string m_Name;
public:
Player(const std::string& name)
: m_Name(name){}
std::string GetName() { return m_Name;}
};
void PrintName(Entity* entity){
std::cout >> entity->GetName() << std::endl;
}
int main()
{
Entity* e = new Entity();
PrintName(e);
Player* p =new Player("Louis");
PrintName(p);
std::cin.get();
}
Entity get print twice and not Louis .
Because it go inside intity and call entity, it cause methods refer to the type.
But we want the getName inside Player.
Then we use virtual function, to overwrite the function in the base function and use the subclass function instead.
Class Entity{
public:
virtual std::string getName() { return "Entity";}
};
Class Player: public Entity{
private:
std::string m_Name;
public:
Player(const std::string& name)
: m_Name(name){}
std::string GetName() override{ return m_Name;}
};
void PrintName(Entity* entity){
std::cout >> entity->GetName() << std::endl;
}
int main()
{
Entity* e = new Entity();
PrintName(e);
Player* p =new Player("Louis");
PrintName(p);
std::cin.get();
}
We add virtual in the base class in front of the fct name.
We add override in then end of the name of the function that overwrite the fct in the base class. (c++11). (it's not a must but it helps for to make the code more clear and gives feedback on error in the name or else)
How does it work ?
Dynamic dispach using V-table, mapping for all the virtual function inside the base class to map them to the overwrite function.
Additional memory use when using virtual function.
Go trough the table to map the function.
The impact is still minimal.
Allow us to define a function in a base class, that does not have an implementation and force subclass to implement that fct.
We want to force the subclass to provide its own definition.
It s commun to do a base class full of undefined methods and force the subclass to implement them, it's called an interface, a class that only consist on unimplemented methods, acting like a template.
It's not possible to instentiate that class. (Instentiate = Entity entity)
virtual std:string getName() = 0; // =0 make a pure virtual fct that has to
// be implemented in an another fct
Class Printable{
public:
virtual std::string GetClassName() =0;
};
Class Entity : public Printable {
public:
virtual std::string getName() { return "Entity";}
std::string GetClassName() {return "Entiry";}
};
Class Player: public Entity{
private:
std::string m_Name;
public:
Player(const std::string& name)
: m_Name(name){}
std::string GetName() override { return m_Name;}
std::string GetClassName() {return "Player";}
};
void PrintName(Entity* entity){
std::cout >> entity->GetName() << std::endl;
}
void Print( Printable* obj){
std::cout << Entity->getName() << std::endl;
}
int main()
{
Entity* e = new Entity();
Player* p =new Player("Louis");
Print(e);
Print(p);
std::cin.get();
}
There is no interface keyworld in C++, it's just a class.
Object oriented programming. How visible members or methods are visible, who can see them, who can use them.
No effect on program performance, right better code. Organization sugar.
Private, Protected, Public. Private :
class Entity{
private: //only this Entity class can access this variable, read and write.
int X, Y;
};
friend can access private variable.
Protected :
class Entity{
protected: //only this Entity class can access this variable, read and write.
int X, Y;
};
More visible than private, but less than public. This class and the subclass can access variables and methods, but not outside the class, for example inside the main.
Public :
class Entity{
public: //only this Entity class can access this variable, read and write.
int X, Y;
};
Everyone can access, class, subclass and outside.
Private tells you shouldn't be accessing this outside the class. It helps you understand a code. We can ensure that people don't break things, called something than his not supposed to be called.
A collection of elements, a way to represent a collection of variables.
Having multiple variables inside a single variable.
Start with 0 in C++
int main{
int example[5];
example[0] =2;
example[4] =4;
}
If you have an index problem (memory access violation), writing outside the bounds of the array, you will have memory address, you will access memory that don't belong to your array variable. Always write in the bounds of your array, hard to debug, you have write in memory that you don't know may be in another variable memory.
For loop is a good way to go through the entire array.
Memory: you have 4 segments of 4 bits (int = 4 bit) each in the example above, one after another in a row.
An array is an integer pointer to the block of memory that contains the 4 integers.
int main{
int example[5];
int* ptr = example;
example[2] =5;
*(ptr + 2) = 5; // same meaning of the line above but takes more bits
*(int*)((char*)ptr + 8) = 5; //same meaning, with same bits
}
When we are doing ptr arithmetic, in term of bits, the numbers of bits he is going to add depend on the type. For example, above we are going to add two times 4 bits because it's integer.
Array on the heap
int main{
int example[5]; //will be deleted at the end of main
int* another = new int[5]; //needs to be manualy deleted using delete keyword (delete[])
for (int i=0, i<5,, i++){
example[i] = 2;
anotger[i] = 2;
}
delete[] another;
}
Why dynamically allocate in the heap ? Lifetime. But It takes a lot of performance.
int count = sizeof(example) / sizeof(int); //to know the size of the array only on the stack
Not very beautiful and not very trust-able.
Other way :
class Entity{
public:
static const int exampleSize = 5;
int example[exampleSize]; //has to a compile time known const = static const
Entity()
{
for (int i =0; i<exampleSize; i++)
example[i] = 2;
}
};
In C++ 11 :
class Entity{
public:
std::array<int, 5> example; //type,size
Entity()
{
for (int i =0; i<example.size(); i++)
example[i] = 2;
}
};
It's worth it to use c++11 array usually. It's safer because it maintained size.
They are called standard array and int array[5] are called raw array.
An array of characters.
int main{
const char* name = "Louis"; //we can not change the content with const
char name[5]= { 'L', 'o', 'u','i', 's',0} //equal to the line above
}
ASCII representation in memory. A string always ends with a 00 bit in memory. That's how the computer now, where the string end.
In C++:
#include <string>
int main{
std::string name = "Louis";
std::string name = std::string("Louis") + "Hello"; //adding strings
bool contains = name.find("lo") != std::string::npos; //npos position
name.size();
}
You should use the std string library. " string " : (double quote) means const char array.
void PrintString(std::string string){
//Here we are copying string, thats very slow.
std::cout << string << std::endl;
}
void PrintString(const std::string& string){
//.Reference = won't be copied, const = means we promise not changing the string
std::cout << string << std::endl;
}
String copying is very slow, try to avoid.
String Literals = "Louis"
const char* name ="Louis"; // const remind you that you never modified a string defined like this
const wchar_t* name2 = L"Louis"; //Wide characters
const char16_t* name3 = u"Louis"; //UTF 16
const char32_t* name4 = U"Louis"; //UTF 32
string_literals library:
using namespace std::string_literals; //c++14, you can put symbol in front or end of ""
std::wstring name0 = u8"Louis"s + U"Hello"; //the s at the end mean stand string
const char* example = R"(Line1 //multi lines strings, R stands for roll
Line2
Line3
Line4)";
mutable with const: Can be change.
class Entity{
private:
std::string m_Name;
mutable int m_DebugCount = 0;
public:
const std::string& GetName() const
{
m_DebugCount++;
return m_Name;
}
};
int main{
Entity e;
e.GetName();
}
mutable with lambda: a lambda is a throw away fct.
int main{
Entity e;
e.GetName();
int x = 8;
auto f = [=]() mutable //ref = & value = =
{
x++
std::cout << x << std::endl;
};
f();
}
it's very rare to use mutable in a lambda fct, it's not a common case.
(Member initialiser list) It's a way to initialize our class members fct in the constructors.
class Entity{
private:
int m_score;
std::string m_Name;
public:
Entity()
: m_score(0), m_Name("Unknown") //member initializer list, in the same order that your declare the members
{
// m_Name ="Unknown"; //we can delete this now this we initialize above
}
Entity (const std::string& name)
:m_Name(name)
{
// m_Name = name;
}
const std::string& GetName() const
{
return m_Name;
}
};
int main{
Entity e;
e.GetName();
}
There is a difference of performance, using member initialiser list is more performante because when you don't you create one initialization with the default constructor and one with the actual constructor. See the example done here:
Example:
class Example{
public:
Example()
: m_Name("Unknown")
{
std::cout<< "Created Entity!" << std::endl;
}
Example(int x)
{
std::cout<< "Created Entity with " << x << std::endl;
}
const std::string& GetName() const
: m_Name(name)
{
}
};
class Entity{
private:
Example m_Example; // create an Example here, that will be thrown away
std::string m_Name;
public:
Entity()
: m_Example(8) //or m_Example(Example(8)) // with this we created only one entity
{
// m_Example = Example(8); //and here so we have two object instead of one
}
Entity (const std::string& name)
:m_Name(name)
{
}
};
int main{
Entity e;
}
It's not only a matter of style, it actually make the code more performante ! Should use this all the time !!!!
? : -> Syntactic sugar for If statements
If statement in one line !
static int s_level = 1;
static int s_Speed = 2;
int main{
if (s_Level >5)
s_Speed = 10;
else
s_Speed = 5;
}
==
static int s_level = 1;
static int s_Speed = 2;
int main{
s_Speed = s_Level >5 ? s_Level > 10 ? 15: 10 : 5; //conditions true ? yes : no
s_Speed s_Level > 5 ? 15: 10 : 5;
}
If there is multiple statements don't use ternary operator, it get confusing.
Stack and Heap are the two main memories allocation in C++
Instantiate object :
class Entity{
private:
std::string m_Name;
public:
Entity () : m_Name ("Unknown"){}
Entity (const std::string& name)
:m_Name(name){}
const std::string& GetName() const { return m_Name;}
};
int main{
Entity e; //Instantiate on the stack Object/Name
Entity e("Louis"); // with a parameters
Entity e = Entity("Louis"); //same as line above
e.GetName();
}
It' s the fastest way to instantiate in C++, if you can al ways do it like the example above.
Instantiate on the heap: Why ?
- if we want that the object to live after a scoop end, for example outside a fonction where we instantiate our object
- Not enough memory in the stack.
class Entity{
private:
std::string m_Name;
public:
Entity () : m_Name ("Unknown"){}
Entity (const std::string& name)
:m_Name(name){}
const std::string& GetName() const { return m_Name;}
};
int main{
Entity* e;
{ //empty {}
Entity* entity = new Entity e("Louis"); //new allocate on the heap
e = entity;
e.GetName();
}
}
Allocates on the heap takes longer than allocates on the stack, performance is slower on the heap than stack You have to free manually the memory on the heap (with delete entity;)
Always create on the stack unless you can't !
Stack is usually 1MB
Allocate memory on the heap. Based on the memory you need (int, array, double ,...),it needs to find an address where I can find the memory needed. It's slower than using the stack, because you have more step, the computer has to find the memory.
int main(){
int a = 2;
int* b = new int[50]; // 200 bytes of memory
Entity* e = new Entity(); // here it called also the constructor and allocate memory on
// the heap for one Entity
Entity* e = new Entity[50]; //Allocate memory on the heap for 50 object Entity
Entity* e = new(memory_address) Entity(); //you can choose the address where it's allocate // on the heap
delete e; //neeed to release the memory on the heap by using delete
delete[] b; //delete using [] if you define new with []
}
new call first malloc(sizeof(int)) behind the scene ( memory allocation)
class Entity{
private:
std::string m_Name;
int m_Age;
public:
Entity (const std::string& name)
:m_Name(name), m_Age(-1){}
Entity(int age)
: m_Name("Unknown"), m_Age(age){}
};
int main{
Entity a("Louis");
Entity b = Entity(22);
Entity a = "Louis"; //Implicit conversion
}
Doesn't look very clear when you use implicit conversion, try to avoid.
class Entity{
private:
std::string m_Name;
int m_Age;
public:
explicit Entity (const std::string& name)
:m_Name(name), m_Age(-1){}
explicit Entity(int age)
: m_Name("Unknown"), m_Age(age){}
};
int main{
Entity a("Louis");
Entity b = Entity(22);
Entity a = "Louis"; //Implicit conversion
}
We use explicit, when we want the constructor to be explicitly called. When we don't want to have the compiler doing implicit conversion we don't want.
It's not often used, can be used in some mathematical object for example when we don't want the compiler to convert vector to number all the time.
Operators are just fonctions. Helps to clean the code.
Operator overloading : give a new meaning to an operator, adding to an operator, we are allowed to change the behavior of operator.
operator overloading should be used as minimum as possible, and only used when It makes perfect sens, and not all the time.
Operator Overloading example: Without:
struct Vector2{
float x, y;
Vector2(float x, float y)
:x(x), y(y){}
Vector2 Add(const Vector2& other) const //not going to modify the class so const, passing by reference avoid copying
{
return Vector2(x +other.x, y + other.y);
}
};
int main(){
Vector2 position (4.0f, 4.0f);
Vector2 speed(0.5f, 1.5f);
Vector2 result = position.Add(speed);
}
With:
struct Vector2{
float x, y;
Vector2(float x, float y)
:x(x), y(y){}
Vector2 Add(const Vector2& other) const //not going to modify the class so const, //passing by reference avoid copying
{
return Vector2(x +other.x, y + other.y);
}
Vector2 operator+(const Vector2& other) const //2 way of writting
{
return *this + other;
}
Vector2 operator+(const Vector2& other) const //Same
{
return Add(other);
}
};
int main(){
Vector2 position (4.0f, 4.0f);
Vector2 speed(0.5f, 1.5f);
Vector2 result = position.Add(speed);
Vector2 result2 = position + speed;
}
this: a key word accessible throw a method (fct of a class). It's a pointer to the curent object instance that the method belongs too.
void PrintEntity(Entity* e);
class Entity{
public:
int x,y;
Entity(int x, int y)
{
Entity* e = this;
this->x=x;
PrintEntity(this)
}
int GetX() const{
const Entity* e = this;
return this->x
}
};
void PrintEntity(Entity* e){
}
Stack is a data structure.
We stack things on top, like a stack of books, where you need to remove the book from above to access books that are in the middle. When you enter a scope({ }), you add a book to your stack and when you declare variable you write stuff inside your books. When you are out of the scope, it's like you are not anymore inside the book, so it's gone !
***On the stack, a variable will be gone when you go out of scope. ***
class Entity{
public:
Entity()
{
cout<<"Created"<<endl;
}
~Entity()
{
cout<<"destroyed"<<endl;
}
};
class ScopedPtr{
private:
Entity* m_Ptr;
public:
ScopedPtr(Entity* ptr)
: m_Ptr(ptr)
{
}
~ScopedPtr()
{
delete m_Ptr;
}
};
int main(){
Entity e; //get destroyed at the end of scope
Entity* e = new Entity(); //doesn't get destroyed at the end of scope
ScopedPtr e = new Entity(); // get destroyed at the end of scope
Automate the process of allocate and delete pointers on the heap without calling new or delete.
unique_ptr: scope pointer, when it goes out of scope the ptr on the heap get delete.
you need to #include < memory> to use smart pointers
int main(){
std::unique_ptr<Entity> entity(new Entity()); //create unique_ptr
std::unique_ptr<Entity> entity = std::make_unique<Entity>(); //better way to create
}
This pointer is unique, you can not copy it for example.
share_ptr: It's usually using reference counting (count how many reference you have to your pointer, when it goes to 0 it gets deleted.
int main(){
{
std::shared_ptr<Entity> e0; //holds reference of the shared ptr
{
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>(); //created shared_ptr
e0 = sharedEntity;
}//doesn't die in this scope
}//die in this scope because e0 gets delete, because no more reference !
}
You can copy with shared pointers.
weak_ptr : When you assign shared ptr to weak ptr it won't count has a reference, so It won't keep shared ptr alive. It won't keep shared ptr alive. You can ask weak_ptr: are you expired, are you still valid
int main(){
{
std::weak_ptr<Entity> e0; //doesn't hold reference of the shared ptr, just copy
{
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>(); //created shared_ptr
e0 = sharedEntity;
}//die in this scope, because no more reference, weak_ptr doesn't count as a reference.
}
}
Try to use smart pointers as much as possible, it's very useful and optimized.
struct Vector 2{
float x,y;
};
int main(){
Vector2 a = {2,3};
Vector2 b = a; // a and b are 2 differents variables, we are copying.
b.x = 2; // a will reamain the same, we are copying the value into b.
Vector2* a = new Vector2();
Vector2* b = a; // we are copying the ptr that have the same value.
b++; // a will still be the same, we changed the b ptr.
b->x = 2; //will change a and b, because we are changing the value on the same memory address
}
When we are assigning one variable to another (variable = variable), we are always copying except for reference.
Deep copy, copy the entire object, for example it will also copy the content of pointers or where the pointer is pointing too.
Copy_constructor: used to do a deep copy of an object, used to copy object, that for example have a memory address as members and we need to have two different memory address and not the same.
String(const String& other){ // to add to the object declaration;
memcpy(this, &other, sizeof(String));
memcpy(m_buffer, other.m_Buffer, m_Size + 1);
}
->: it's to access a member function or a member variable or reference of an object through a pointer (with a regular variable or reference you would use . )
Entity e;
e.Print();
Entity* ptr = &e;
(*ptr).print();//de-reference first then call .print()
ptr->print();//same effect as a line above but simpler and cleaner.
It's possible to overload the array operator
Example of overloading ->:
class Entity{
public:
void Print() const { std::cout <<"Hello" << std::endl;}
};
class ScopedPtr{
private:
Entity* m_obj;
public:
ScopedPtr(Entity* entity)//constructor
: m_obj(entity)
{
}
~ScopedPtr()//destructor
{
delete m_obj;
}
Entity* operator->(){ return m_obj;} //overload the -> operator
};
int main(){
ScopedPtr entity = new Entity();
entity->Print();
}
Get the offset of value in memory:
struct Vector3{
float x,y,z; //offset of x =0, y= 4 z= 8 in the memory (float is 4 bits)
};
int main(){
int offset = (int)&((Vector3*)nullptr)->x;
cout<< offset<<endl;
}
It's an array list, a dynamic array. It can be resize, it doesn't have a fix size, putting element in the array will make it bigger.
standard_template_library: A library field with containers, contains certain type of data, the data type that the container contains is up to you.
std::vector should not be called vector but array list, it has nothing to do with the mathematical array.
How does it work ? It creates a new array bigger and delete the old one every time you add an element. It's not really optimized.
vector<Type> vect;
will allocate the vector
, i.e. the header info, on the stack, but the elements on the free store ("heap").
vector<Type> *vect = new vector<Type>;
allocates everything on the free store.
vector<Type*> vect;
will allocate the vector
on the stack and a bunch of pointers on the free store
#include <iostream>
#include <vector>
struct Vertex{
float x,y,z;
};
std::ostream& operator<<(std::ostream& stream, const Vertex& vertex){
stream << vertex.x << "" << vertex.y << "" << vertex.z;
return stream; //overloading << to make cout work with our struct
}
int main() {
std::vector<Vertex> example2;
example2.push_back({1,2,3});
example2.push_back({4,5,6});
std::vector<int> example;
example.push_back(1);
example.push_back(2);
for (int v : example) //here we copy, but with int we don't care
std::cout<<v<<std::endl;
std::cout<<example[0]<<std::endl;//[] is overload for std::vector
for (Vertex& v : example2) //passing by reference here we don't copy a new object all the time
std::cout<<v<<std::endl;
example.erase(example.begin() +1); //will delete the second element of the array
example.clear();
void Function(const std::vector<int>& example){} //pass vector by reference in fct so we don't copy, by const if we don't modify.
}
We need to continuously re-allocate memory large enough to handle new elements and copy all previous existing elements to the new location when we push_pack new element.
How can we avoid copying our object ?
Knowing when copying happened is very important for optimization !
#include <iostream>
#include <vector>
struct Vertex{
float x,y,z;
Vertex(float x, float y, float z) //default constructor
: x(x), y(y), z(z) {
}
Vertex(const Vertex& vertex) //copy_constructor
: x(vertex.x), y(vertex.y), z(vertex.z)
{
std::cout << "Copied " << std::endl;
}
};
std::ostream& operator<<(std::ostream& stream, const Vertex& vertex){
stream << vertex.x << "" << vertex.y << "" << vertex.z;
return stream;
}
int main() {
std::vector<Vertex> example2;
example2.reserve(3); // 2) optimization, we are telling vector we would like
// enough memory to store 3 objects example2.emplace_back(1, 2, 3); // 1) optimization
example2.emplace_back(5, 4, 6);
example2.emplace_back(7, 8, 9);
}
Optimization strategy: Using the copy_constructor we know how many times we copied our object.
-
We are constructing in the stack memory of the main function, then we copied
main function to the actual vector class memory. We should construct this vector
in the actual memory that the vector has allocate for us. -
We resized the vector twice so we copied twice more the object, if we know how
many element we are going to push, we can optimize the initial size of vector.
CmakeList Needs to be completed !!!
How can we use external library in our project. Include files (header files) Lib files
- create Dependencies folder
- create a folder with the name of the library
- include & lib
Using dynamic libraries:
Usually a fonction can only return one type (if we want to return a string and an int we have a problem) and if want to return multiple variables of the same type we can use an array but it's not optimized.
Input parameters way: We can pass by reference argument of the fonction and modified directly the value inside the memory address. Example:
void Function (std::string& outHello, std::string& outHello2){ //output arguments.
outHello = "Blabla";
outHello2 = "bibi";
}
void Function2 (std::string* outHello, std::string* outHello2){ //output arguments.
(*outHello) = "Blabla";
(*outHello2) = "bibi"; //with this method you can pass by argument a nullptr.
}
int main(){
std::string hello, hello2;
Function(&hello, nullptr);
std::cout << hello << std::endl;
std::cout << hello2 << std::endl;
Using an array We can use an array to store multiple values and return the array. We can also use a vector, array are created on the stack, vector will stored on line storage on the heap, so array will be faster.
Struct way:
struct OutStrings{
std::string Hello;
std::string Hello2;
};
OutStrings (arguments){
return (Hello, Hello2)
}
You can get the compiler writing code for you based on a set of rules.
void Print (int value){
std::cout << value << std::endl;
}
void Print (float value){
std::cout << value << std::endl;
}
void Print (std::string value){
std::cout << value << std::endl;
}
int main(){
print(5);
print(5.2f);
print ("hello");
}
==
template<typename T> // template parameters
void print (T value){ //template
std::cout << value << std::endl;
}
int main(){
print<int>(5);
print(5.2f);
print ("hello");
}
The template doesn't exist before we called it. Template specified how to create methods, fonctions, class.
Example for a class:
template<typename T,int N>
class Array
{
private:
T m_array[N];
public:
int getSize() const {return N;}
};
int main(){
Array<int,5> array;
std::cout << array.getSize() << std::endl;
}
The stack and heap are two areas of memory we have in our ram.
The performance difference is done during the allocation. Allocating on the stack is much faster, you just stack one above the other in memory, just one line a CPU instruction. Allocating on the heap is much more heavy, using new called malloc()that go throw the free list of memory, search for a block of memory of the size we need allocate it, reserved it (no one can use it after), lot of CPU instructions. Delete also is very heavy in CPU instructions.
Get evaluated during pre-processing Used to define const, or fonction or other stuff at the beginning of the script.
#define LOG(x) std::cout << x << std::endl
#if ==1
#define
#ifdef blabla
#elif
#else
#endif
int main(){
LOG("Hello");
}
\ to go to the line in a macro instruction. used a lot for debug
Will automatically deduce the type of a variable (class, int, float, string, ...). With auto we can change cpp to be a language where we don't specifies the type everywhere.
int main(){
int a =5;
int b = a;
auto b = a;
auto c = "Louis"; // c will be a char ptr*
Auto will be able to adapt if you change code. Example:
char* getName(){
return "Louis";}
int main(){
auto name =getName() //name will be a char* but if I change the return type of the fct to std::string, name will be a std::string without changing any code in main.
}
Here it's not a very good idea to use auto because your code is less clear, and you can break the code without notice it.
Useful example of auto:
char* getName(){
return "Louis";}
int main(){
std::vector<std::string> strings;
string.push_back("Apple");
string.push_back("Orange");
for (std::vector<std::string>::iterator it = strings.begin(); it!= strings.end(); i++)
{
std::cout<< *it<< std::endl;
}
==
char* getName(){
return "Louis";}
int main(){
std::vector<std::string> strings;
string.push_back("Apple");
string.push_back("Orange");
for (auto it = strings.begin(); it!= strings.end(); i++)
{
std::cout<< *it<< std::endl;
}
Auto helps clean your code, when tou have massive types before a variable name. You can also use:
using iterator = std::vector<std::string>::iterator;
for (iterator it = strings.begin(); it!= strings.end(); i++)
When you use auto, you still have to specify the reference.
Array that don't grow, you cannot touch the size of this array
int main(){
std::array<int,5> data; //standard array, new style
data.size(); //only std array
int data[5]; //old style, same meaning, but we need to maintain the size.
}
Static Arrays are stored on the stack.
Standard array: It doesn't store the size, it's a template argument, same memory usage.
Exercice:
#include <iostream>
#include <vector>
#include <array>
template<typename T, size_t Size>
void PrintArray(const std::array<T, Size>& array){
for (decltype(array.size()) i = 0; i < Size ; i++) { //decltype very usefull if we don't know the type in advance
std::cout<<array[i]<<std::endl;
}
}
int main(){
std::array<double, 6> a;
a[0] =1;
PrintArray(a);
for (auto &item : a) {
std::cout<<item<<std::endl;
}
}
Raw functions pointers, a way to assign a function to a variable.
void Hello()
{
cout << "Hello" << endl;
}
typedef void(*HelloWorldFunction);
int main(){
auto function = &Hello;
auto function = Hello; //working the same as above, implicit conversion
HelloWordFunction function = Hello; //working the same using typedef
function(); // print "Hello"
}
Example:
void PrintValues(int value)
{
std::cout << "Value: " << value << std::endl;
}
void ForEach(const std::vector<int>& values, void(*func)(int)){
for (int value : values){
func(value);
}
}
int main() {
std::vector<int> values = { 1, 2, 3 ,4, 5};
ForEach(values, PrintValues);
}
C++ reference about lambda Anonymous functions. Using a function without declaring it formally. When we have a fonction pointer, we can use a lambda. A lambda is a way to define a function without declaring a function.
void ForEach(const std::vector<int>& values, void(*func)(int)){
for (int value : values){
func(value);
}
}
int main() {
std::vector<int> values = { 1, 2, 3 ,4, 5};
ForEach(values, [](int value){ std::cout << "Value: " << value << std::endl;}); //lambda
}
A lambda function is a good way to specify code that we want to run some time in the future, we run the code when we run throw the elements of our vector.
auto lambda = [](int value){ std::cout << "Value: " << value << std::endl;});
[] = the captures, it say how we want to pass the variable to the function, by value or by reference if we want to put outside variable inside our lambda function. (= by value, & = by reference)
Example of lambda use: std::find_if:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> values = { 1, 2, 3 ,4, 5};
auto it = std::find_if(values.begin(), values.end(), [](int value){ return value >3;});
std::cout << *it << std::endl;
}
Namespace exist to avoid naming conflict. W can have two fonctions that have the same name but in different namespace. Example:
namespace apple{
void print(std::string text)
{
std::cout << text << std::endl;
}
}
namespace orange{
namespace function{
void print(std::string text)
{
std::string temp =text;
std::reverse(temp.begin(), temp.end());
std::cout << temp << std::endl;
}
}
}
int main(){
using namespace apple;
print("hello");//will use apple namespace because of using namespace
using apple::print; //will be applied only to the fonction print of apple
print("hello");//will use apple namespace because of using namespace
orange::function::print("Hello");
}
a class is a namespace on his own.
We can use namespaces inside a function, and it will be deleted when we reach the end of scope.
Never use namespace in a header file, very hard to debug and you never know where it will be applied, you will have naming conflict.
Work in parallel.
#include <threads>
static bool s_Finished = false;
void DoWork(){
while (!s_Finished){
cout << "working" << endl;
std::this_thread::sleep_for(5s); //this_thread give command to this current thread.
}
}
int main(){
std::thread worker(DoWork); //take a fct pointer as arguments. Will execute everything in DoWork (we are making the thread object here)
std::cin.get() // wait for the user to press enter
s_Finished = true;
worker.join(); //wait for this thread to finish, block the current thread (main, here) to wait that the worker thread finished
}
Threads is used to speed up your program, It has been made for optimization purpose.
How to time our program, how fast our program run, how to execute a task at a certain time. Chrono Library.
#include <chrono>
#include <threads>
struct Timer{
std::chrono::time_point<std::chrono::steady_clock> start,end;
std::chrono::duration<float> duration;
Timer()
{
start = std::chrono::high_resolution_clock::now()
}
~Timer()
{
end = std::chrono::high_resolution_clock::now();
duration = end - start;
std::cout << duration.count() << std::endl;
}
};
void Function(){
Timer timer;
for (int i = 0; i<100; i++)
std::cout << "hello";
}
int main(){
auto start = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(1s);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> duration = end - start;
std::cout << duration.count() << std::endl;
Function();
}
This program compute the time duration between our line of code. In a second step, we created a struct to do actually to the timing automatically without having to write the 6 lines of code all the time ( the timer will start in the constructor and when we reach the end of the scoop of the function, the destructor is called with the end and the duration of the timer).
Can be use to do bench marking.
An array of arrays. An array is a pointer to the beginning of the array. An array of array, is an array of pointer (buffer) that points to arrays.
int main()
{
int* array = new int[5]; //memory allocation, setting the size of the allocation, array
int** a2d = new int*[5] //a pointer to a pointer integer, 2D array
a2d[0] = nullptr; //refer to and int pointer
array[0] = 0; //refer to an int
for (int i=0; i<5;i++)
a2d[i]= new int[5];
for (int i=0; i<5;i++)
delete[] a2i[i];
delete[] a2d;
}
Leads to memory fragmentation, we don't know where it's allocated in memory, random, they can be close to each other or very far -> cache miss. A lot slower than single dimensional array.
Tips, creating a single dimensional array and you can still increment in a double loop with line and column:
int* array = new int[5 * 5];
for (int y = 0; y<5; y++)
{
for (int x = 0; x<5; x++)
{
array[x + y * 5] = 2; //5 is the width of the array, every 5 element we jump of the width
}
}
Much faster !
using std::sort.
int main()
{
std::vector<int> values = {1,3,5,7,4,2};
std::sort(values.begin(), values.end());
std::sort(values.begin(), values.end(), [](int a, int b)
{
if (a == 1)
return false
if (b == 1)
return true
return a>b; //the list will be sort in ascending order with 1 at the end.
}); //using a lambda fct
std::sort(values.begin(), values.end(), [](int a, int b)
{
return a>b; //
}); //using a lambda fct
for (int value : values)
std::cout << value << std::endl;
}
In cpp, we have a type system but we can overpass it using memory access. Not very clear..., on this.
int main()
{
int a = 50;
double value = a;
//=
double value = (double)a;
double value = *(double*)&a;
}
It's a bit like a class type or a struct type. It only can occupied memory of one member at a time. For example if i have 4 floats in a unions, if i change the value of one member it will change the value for others because they are stored on the same memory.
struct Vector2
{
float x,y;
};
struct Vector4
{
union
{
struct
{
float x,y,z,w;
};
struct
{
Vector2 a,b;
};
};
};
Multiple ways to address the same data.
//C style
double value = 5.25;
int a = (int) value; //Explicit convertsion
double a = (int) value + 5.3;
int a = value; //Implecit convertion
//Static cast C++ ay
double s = static_cast<int>(value) + 5.3op
C++ way is more a syntax sugar, it is not doing anything more that the C style but It is easier to search for the cast when you write it in a c++ way.
//Dynamic cast
class Derived : public Base
{
public:
Derived(){}
~Derived(){}
};
class AnotherClass : public Base
{
public:
AnotherClass(){}
~AnotherClass(){}
};
int main()
{
Derived* derived = new Derived();
Base* base = derived;
//Compile time checking, run time checking, see if it actually work
AnotherClass* ac = dynamic_cast<AnotherClass*>(base)
if (ac)
{
}
}
const cast is used to add or remove const. re interpret cast is used to re interprete the memery