Here is an example of overloading >>
and <<
operator so that we can print out own class out to console. Note that the std::cout << "Hello World" << std::endl
is overloaded by default.
std::cout << "Hello World"
will be called first and then the return value will be called with<< std::endl
.std::endl
has two functions: (1) make the cursor on the next line. (2) make sure flush to console immediately.
#include <iostream>
using namespace std;
class Test {
int x; // this is private object
public:
Test(int x = 0): x {x} {}
// `friend` means the function can use private properties (such as [x] of the class)
friend istream& operator >> (istream& input, Test& obj);
friend istream& operator << (ostream& input, Test& obj);
};
// Detailed Explanation: [Youtube](https://www.youtube.com/watch?v=2972LRdyquk)
istream& operator >> (istream& input, Test& obj) {
input >> obj.x;
return input; // return type is `istream&`
}
ostream& operator << (ostream& output, Test& obj) {
output << obj.x << endl;
return output;
}
int main() {
Test t;
cin >> t;
cout << t;
return 0; // success
}
But honestly, in above example using namespace std
is a bad practice. Here is why:
Usually if we need to do string name = "something"
then we actually need std::string name = "something"
. This is because internally, std
is following:
namespace std {
string;
istream& cin();
ostring& cout();
}
However, since people get lazy and don't want to type std::string
, they using namespace std
and then type string name = "something"
.
A better practice is to do the following:
#include <iostream>
using std::cout;
using std::endl;
using std::string;
public function with the same function name as class name is the constructor.
You can have "default constructor", "copy constructor", "move constructor"
class A {
public:
A(){} // default constructor
A(const A&){} // copy constructor
A(const A&&){} // move constructor
}
Note in above example, we can ignore the object name and only write type in declearation.
int main(int argc, const char* argv[]) {
A a; // call default constructor
A b = a; // call copy constructor
A c = std::move(a) // call move constructor
A d(a); // call copy constructor
A e(std::move(a)); // call move constructor
}
Sometimes variables can be constructed in implicit way:
std::unique_ptr<Entity> entity = new Entity();
instead of explicit way:
std::unique_ptr<Entity> entity(new Entity())
Note that if
Entity()
throw exception, there might be potential memory leak. you should dostd::unique_ptr<Entity> entity = std::make_unique<Entity>();
to be safe.
You can force non-implicit by doing something like this:
explicit unique_ptr(pointer _Ptr) {
...
}
A copy constructor is a member function that initializes an object using another object of the same class. A copy constructor has the following general function prototype:
ClassName (const ClassName &old_obj);
Copy constructor is used to initialize the members of a newly created object by copying the members of an already existing object. Not ethat the copy constructor is a shallow copy. Deep copy is possible only with a user-defined copy constructor.
MyClass t1, t2;
MyClass t3 = t1; // ----> (1)
t2 = t1; // -----> (2)
Note that (1) calls the copy constructor and (2) calls the assignment operator
In C++11
, you might see default
or delete
keyword in constructor declearation.
class C {
C(const C&) = default;
C(C&&) = default;
C& operator=(const C&) & = default;
C& operator=(C&&) & = default;
virtual ~C() { }
};
= default
make use of compiler-generated version of that function, so you don't need to specify a body. Compiler can generate constructors, destructors and assignment operators.
Also notice that a default constructor would not be generated if you provide any other non-default constructor. If you still want the default constructor, too, you can use this syntax to have the compiler make one.
Use = delete
to specify that you don't want the compiler to generate that function automatically.
Move constructor isn't always generated by default (e.g. if you have a custom destructor)
public function with the same function name as class name but with a ~
in front is the deconstructor.
If you have written code like following
#include <iostream>
using namespace std;
class T1 {
const int t = 100; // Note t is not static, therefore the instant of T1 has not been created yet. You can't assign value of a const int that does not exist.
public:
T1() {
cout << "T1 constructor: " << t << endl;
}
};
The compiler will tell you:
test.cpp:21: error: ISO C++ forbids initialization of member ‘t’
test.cpp:21: error: making ‘t’ static
This is because const
variable is not modifiable. Instead, you need to initialize const int t
in the constructor:
T1(int val) : t(val) {
// Other constructor stuff here
}
The syntax t(val)
is executed as a "initialization list". For example, the above code is nearly the same as the following code with minor difference.
T1(int val) {
t = val;
// Other constructor stuff here
}
By the C++
specification, the above code illegal. We cannot change the value of a const variable in the constructor, because it is marked as const
. So you have to use the initialization list.
Uniform initialization is a feature in C++ 11 that allows the usage of a consistent syntax to initialize variables and objects ranging from primitive type to aggregates. In other words, it introduces brace-initialization that uses braces {}
to enclose initializer values. The syntax is as follows: type var_name{arg1, arg2, ....arg n}
The advantage is that the initialization works and looks the same for all object including array, built-in, and objects. Using bracket {}
instead of ()
allows the compiler to distinguish initialization and function declearations more easily.
Following are some of the examples of the different ways of initializing different types:
int i; // uninitialized built-in type
int j = 10; // initialized built-in type
int k(10); // initialized built-in type
int a[] = {1, 2, 3, 4} // Aggregate initialization
X x1; // default constructor
X x2(1); // Parameterized constructor
X x3 = 3; // Parameterized constructor with single argument
X x4 = x3; // copy-constructor
If initialized using brace initialization, the above code can be re-written as:
int i{}; // initialized built-in type, equals to int i{0}
int j{10}; // initialized built-in type
int a[]{1, 2, 3, 4} // Aggregate initialization
X x1{}; // default constructor
X x2{1}; // Parameterized constructor
X x4{x3}; // copy-constructor
In most cases {}
and ()
are the same for initialization. Minor differences involving the use of std::initializer_list<>
When using inheritance, you can use initialization list to initialize your parent:
#include <iostream>
class Foo {
public:
Foo( int x ) {
std::cout << "Foo's constructor "
<< "called with "
<< x
<< std::endl;
}
};
class Bar : public Foo {
public:
Bar() : Foo( 10 ) { // construct the Foo part of Bar
std::cout << "Bar's constructor" << std::endl;
}
};
int main() {
Bar stool;
}
You can also use initialization list to initialize fields in order:
class Baz {
public:
Baz() : _foo( "initialize foo first" ), _bar( "then bar" ) { }
private:
std::string _foo;
std::string _bar;
};
You can also pass input to initialization list as follow:
class Baz {
public:
Baz(int i) : i(i) { }
private:
int i;
};
Note that it work for primitive type as well as user-constructed type. They have the same syntax thanks for uniform initialization.
i(i)
above will call the copy constructor. Theint
type can also be a template type as long as the template type has a copy constructor:
template <class T>
class my_template {
public:
// works as long as T has a copy constructor
my_template( T bar ) : _bar( bar ) { }
private:
T _bar;
};
Using initialization lists to initialize fields is not always necessary (although it is probably more convenient than other approaches). But it is necessary for const fields. If you have a const field, then it can be initialized only once, so it must be initialized in the initialization list.
Since constructor can generate error, calling initialization list can generate error. You can handel error in the following manner:
class Foo {
Foo() try : _str( "text of string" ) {
} catch ( ... ) {
std::cerr << "Couldn't create _str";
// now, the exception is re-thrown as if we'd written
// "throw;" here
}
};
References are often confused with pointers but three major differences between references and pointers are:
You cannot have NULL references. You must always be able to assume that a reference is connected to a legitimate piece of storage.
Once a reference is initialized to an object, it cannot be changed to refer to another object. Pointers can be pointed to another object at any time. Setting a reference to another referene only change the value of the first reference.
A reference must be initialized when it is created. Pointers can be initialized at any time.
A pointer has its own memory address and size on the stack, whereas a reference shares the same memory address with the original variable but also takes up some space on the stack.
You can have a pointer to pointer (known as a double pointer) offering extra levels of indirection, whereas references only offer one level of indirection. Therefore you cannot put a reference inside a std::vector
.
Various arithmetic operations can be performed on pointers, whereas there is no such thing called Reference Arithmetic (however, you can perform pointer arithmetic on the address of an object pointed to by a reference, as in &obj + 5
).
You can still modify the pointed data with reference. Adding const
guarantee nobody can change a referenced value.
It is undefined behavior when the referenced value is generated on stack and become out of scope.
View this website on How to use Reference
Example:
#include <iostream>
int main () {
int i;
int& r = i;
i = 5;
std::cout << "Value of i : " << i << std::endl;
std::cout << "Value of i reference : " << r << std::endl;
return 0;
}
will print the following:
Value of i : 5
Value of i reference : 5
Therefore, it is very easy to request some modification by passing reference to functions
void swap(int& i, int& j) {
int tmp = i;
i = j;
j = tmp;
}
int main() {
int x, y;
swap(x,y);
}
Note that pointer can be written in a new way:
// old way of doing C (still valid)
int x = 16;
int* y = &x;
cout << *y;
// new way of doing C++
int x = 16;
int& y = x;
cout << y;
Unique Pointer: typical standard pointers you learned in schools. Copying them is not a good idea since, after you make a copy, if the original pointed memory is freed, then copied pointer's memory is also freed, making the copied pointer's reference dangerous to access.
std::unique_ptr<Entity> entity = std::make_unique<Entity>();
This feature kinda replaced
new
anddelete
keyword. Weak Pointer must be used with Shared Pointer
std::shared_ptr<Entity> entity = std::make_shared<Entity>();
For example:
{
std::shared_ptr<Entity> e;
{
std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
e = sharedEntity;
}
// Entity out of scope for se, memory remain
}
// Entity out of scope for e, memory released
Well, what if we want a secure pointer but we just want to store the pointer but we don't want to keep it alive when we store it. We can use Weak Pointer. Unlike Shared Pointer, it doesn't increase reference count. (You can ask a weak pointer whether they are expired.)
std::weak_ptr<Entity> weakEntity = sharedEntity;
When using Weak Pointer, it is a good practice to check whether the pointer is valid and if so, put a lock to it to ensure no concurrent threads can free the memory region.
if (std::shared_ptr< Transform > parent_ = parent.lock()) {
// we have "parent" memory region
} else {
// no memory
}
auto
keyword automatically infer return type.
template <typename YourTypeName1, typename YourTypeName2>
YourTypeName1 functionName(YourTypeName1 parameter1, YourTypeName2 parameter2, ...) {
// code
}
template
keyword means the following function can be used with arbitrary input type.
For example, we can do the following code:
#include <iostream>
using namespace std;
template <typename T>
T add(T num1, T num2) {
return (num1 + num2);
}
int main() {
int result1 = add<int>(2, 3);
cout << "2 + 3 = " << result1 << endl;
double result2 = add<double>(2.2, 3.3);
cout << "2.2 + 3.3 = " << result2 << endl;
return 0;
}
You can also use a template in a class to make a generic stack.
template <class T>
class Stack {
public:
Stack(int = 10);
~Stack() { delete [] stackPtr; }
int push(const T&);
int pop(T&);
int isEmpty()const { return top == -1; }
int isFull() const { return top == size - 1; }
private:
int size;
int top;
T* stackPtr;
};
Note that the keyword
class
andtypename
intemplate
are equivalent. But there are differences outside the usage as described in stackoverflow
Static variable can be either inside a class or inside a function. A static variable is a variable that is declared using the keyword static. The space for the static variable is allocated only one time and this is used for the entirety of the program.
Once this variable is declared, it exists till the program executes. So, the lifetime of a static variable is the lifetime of the program.
// from tiny-cuda-nn
inline std::atomic<size_t>& total_n_bytes_allocated() {
static std::atomic<size_t> s_total_n_bytes_allocated{0};
return s_total_n_bytes_allocated;
}
class 1 {
};
class 2: public 1 {
};
In the above syntax, class 1 is the parent of class 2. The constructor of class 1 is normally executed before the constructor of class 2 when class 2 is created.
In C++11, there are two syntaxes for function declaration:
return-type identifier ( argument-declarations... )
and
auto identifier ( argument-declarations... ) -> return_type
They are equivalent. Now when they are equivalent, why do you ever want to use the latter? Well, C++11 introduced this cool decltype
thing that lets you describe type of an expression. So you might want to derive the return type from the argument types. So you try:
template <typename T1, typename T2>
decltype(a + b) compose(T1 a, T2 b);
however the compiler will tell you that it does not know what a
and b
are in the decltype
argument. That is because they are only declared by the argument list.
You could easily work around the problem by using declval
and the template parameters that are already declared. Like:
template <typename T1, typename T2>
decltype(std::declval<T1>() + std::declval<T2>())
compose(T1 a, T2 b);
except the above expression is getting very long and hard to read. So the alternate declaration syntax was proposed and implemented and now you can write
template <typename T1, typename T2>
auto compose(T1 a, T2 b) -> decltype(a + b);
C++14
also permits just
auto identifier ( argument-declarations... )
as long as the function is fully defined before use and all return
statements deduce to the same type. The ->
syntax remains useful for public functions (declared in the header) if you want to hide the body in the source file. Somewhat obviously that can't be done with templates, but there are some concrete types (usually derived via template metaprogramming) that are hard to write otherwise.
The above is almost-exact copy from stackoverflow
#include <functional> // std::function
#include <utility> // std::move
namespace my {
using std::function;
using std::move;
class Non_copyable
{
private:
auto operator=( Non_copyable const& ) -> Non_copyable& = delete;
Non_copyable( Non_copyable const& ) = delete;
public:
auto operator=( Non_copyable&& ) -> Non_copyable& = default;
Non_copyable() = default;
Non_copyable( Non_copyable&& ) = default;
};
class Scope_guard
: public Non_copyable
{
private:
function<void()> cleanup_;
public:
friend
void dismiss( Scope_guard& g ) { g.cleanup_ = []{}; }
~Scope_guard() { cleanup_(); }
template< class Func >
Scope_guard( Func const& cleanup )
: cleanup_( cleanup )
{}
Scope_guard( Scope_guard&& other )
: cleanup_( move( other.cleanup_ ) )
{ dismiss( other ); }
};
} // namespace my
#include <iostream>
void foo() {}
auto main() -> int
{
using namespace std;
my::Scope_guard const final_action = []{ wclog << "Finished! (Exit from main.)\n"; };
wcout << "The answer is probably " << 6*7 << ".\n";
}
See stackoverflow
Atomic variables are primarily used to synchronize shared memory accesses between threads. But in C++, we have std::atomic<>
atomic objects. Each atomic class has a load() and a store() operation which is utilized to perform assignments. This helps make it clearer when atomic operations are being performed rather than a normal assignment.
atomic_var1.store (atomic_var2.load()); // atomic variables
var1 = var2; // regular variables
Each load()
and store()
function can take in a std::memory_order
that specify how the CPU may schedule the load and store accross threads.
typedef enum memory_order {
memory_order_relaxed,
memory_order_consume,
memory_order_acquire,
memory_order_release,
memory_order_acq_rel,
memory_order_seq_cst
} memory_order;
Using atomic class, the default behavior of some arithmetic operation is guaranteed to be atomic:
std::atomic<long> value(0);
value++; //This is an atomic op
value += 5; //And so is this
In most Instruction Set Architecture (ISA), simple addition
+
and subtraction-
is itself atomic, but not+=
. But withstd::atomic
, both are atomic.
You can overload arithmetic operations so that your overloaded function is atomic. Because operator syntax does not allow you to specify the memory order, these operations will be performed with std::memory_order_seq_cst
by default.
Here is a good article with example to explain each memory order. In short, here is my interpretation (might not be correct): - relaxed: only guarantee operation itself is atomic, execution order within one thread is not guaranteed - sequentially consistent: memory and atomic operations are executed in thread order, one by one. - release/acquire: A release operation prevents ordinary loads and stores from being reordered after the atomic operation, whereas an acquire operation prevents ordinary loads and stores from being reordered before the atomic operation. - consume: same operation as acquire, only with the exception that the ordering guarantees only apply to dependent data. (apparently no major compiler implements it)
There are some methods:
.clear()
: Removes all elements from the vector (which are destroyed), leaving the container with a size of 0.
.reserve()
: Increase the capacity of the vector (cuz vector internally double capacity when append), and allocate memory
.capacity()
: check capacity (different than size)
.push_back()
: append element at the end of vector
.emplace_back()
: same as .push_back()
, but can take in multiple arguments at once to avoid copy. Those arguments are passed into constructors automatically.
The first thing to note is that std::move()
doesn't actually move anything. It changes an expression from being an lvalue
(such as a named variable) to being an xvalue
. An xvalue
tells the compiler: "You can plunder me, move anything I'm holding and use it elsewhere (since I'm going to be destroyed soon anyway)"
In other words, when you use std::move(x)
, you're allowing the compiler to cannibalize x
. Thus if x
has, say, its own buffer in memory - after std::move()
-ing the compiler can have another object own it instead.
A typical use is 'moving' resources from one object to another instead of copying. @Guillaume links to this page which has a straightforward short example: swapping two objects with less copying.
Usually we can write:
template <class T>
swap(T& a, T& b) {
T tmp(a); // we now have two copies of a
a = b; // we now have two copies of b (+ discarded a copy of a)
b = tmp; // we now have two copies of tmp (+ discarded a copy of b)
}
But with std::move()
we can:
template <class T>
swap(T& a, T& b) {
T tmp(std::move(a));
a = std::move(b);
b = std::move(tmp);
}
Think of what happens when T
is, say, vector<int>
of size n. In the first version you read and write 3n elements, in the second version you basically read and write just the 3 pointers to the vectors' buffers, plus the 3 buffers' sizes. Of course, class T
needs to know how to do the moving; your class should have a "move-assignment operator" and a "move-constructor" for class T
for this to work.
Above is copied from stackoverflow
A tuple pair where you are allowed to pack to items together and access the first or second.
You can think of std::initializer_list
as a bracket initialized array that can be used to pass in as arguments. It has no definite type definition. Note that std::initializer_list
is a library and it differs from C's built-in initialization list.
#include <iostream>
#include <vector>
#include <initializer_list>
template <class T>
struct S {
std::vector<T> v;
S(std::initializer_list<T> l) : v(l) {
std::cout << "constructed with a " << l.size() << "-element list\n";
}
void append(std::initializer_list<T> l) {
v.insert(v.end(), l.begin(), l.end());
}
};
template <typename T>
void templated_fn(T) {}
int main() {
S<int> s = {1, 2, 3, 4, 5}; // copy list-initialization
s.append({6, 7, 8}); // list-initialization in function call
std::cout << "The vector size is now 8 ints:\n";
for (auto n : s.v)
std::cout << n << ' ';
std::cout << '\n';
std::cout << "Range-for over brace-init-list: \n";
for (int x : {-1, -2, -3}) // the rule for auto makes this ranged-for work
std::cout << x << ' ';
std::cout << '\n';
auto al = {10, 11, 12}; // special rule for auto
std::cout << "The list bound to auto has size() = " << al.size() << '\n';
// templated_fn({1, 2, 3}); // compiler error! "{1, 2, 3}" is not an expression. It has no type, and so T cannot be deduced
templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK
templated_fn<std::vector<int>>({1, 2, 3}); // also OK
}
The above, according to wiki, will output:
constructed with a 5-element list
The vector size is now 8 ints:
1 2 3 4 5 6 7 8
Range-for over brace-init-list:
-1 -2 -3
The list bound to auto has size() = 3
This is basically make a function that can take arbitrary many of arguments. This is achieved using code generation with templates.
// base case
template<typename T>
T Sum(T arg) {
return arg;
}
// recursive case
template<typename T, typename... Args>
T Sum(T start, Args... args) {
return start + Sum(args...);
}
int main() {
// not recommend sending different types, might mess up result
std::cout << Sum(1.0f, 1u, 3, 4, 7) << std::endl;
}
const
Keywordconst
Class Methodclass Bar {
public:
bool foo() const {
data = 0; // this line is not allowed
}
private:
int data;
}
This means we cannot change the object's field.
const
TypeThe const
keyword can be added anywhere in const
type name. A function cannot change the value of a constant type. A function cannot change the value of a reference if it is constant.
There are different types of cast with different levels of dangerous: static_cast<>()
, reinterpret_cast<>()
, const_cast<>()
, and dynamic_cast<>()
Inline function: inline
keyword is used to tell compiler
"Multiple Definition: multiple definition in a .h
file means probably you try to write a function that is not inline
in header file.
For more, read here
Template function: template
keyword is to virtualize types
template
can only be used in .h
file, so that compiler know what types you need to generate. You have some options:
template void Foo<int>() {...}
, template void Foo<float>() {...}
, ... and have a function for each type. (Read more)Aid compiler's parse: consider boost::function< int() > f;
: this could be interpreted as a filling template with int()
or value compairson between 3 variables.
for more, read here
Typename keyword: typename
is for to aid compiler's parse
Aid compiler's parse: consider t::a &b
: this could mean we want to create a pointer named b
of class t::a
, or it could mean we we want to do &
operation on a member variable t::a
with b
. To tell compiler the following is typename
, we put typename
in front of types.
for more, read here
Sample Error: dependent-name ‘Pipeline<p, P, flags>::Fragment’ is parsed as a non-type, but instantiation yields a type
Some conventions:
In CPP, it is more common to use .hpp
than .h
.
size_t
for loop better than uint32_t
increment in loop using ++i
instead of i++
saves 2~3 instructions
use references arguments to receive output from functions instead of shared pointers
Table of Content