C++
Table of Contents
Syntax
Functions
Pass-by-reference and pass-by-value
By reference does NOT mean that we are working with a pointer
inside
the function! It simply means that operations made to the argument is made
to the original value it refers to.
By value means what it usually means; makes a copy when passed to the function.
void by_val(char a) { a = 'c'; } void by_ref(char& a) { a = 'b'; } int main() { char a = 'a'; std::cout << a << '\n'; by_val(a); std::cout << a << '\n'; by_ref(a); std::cout << a << '\n'; }
Class
friend
class
The friend
declaration appears in a class body and grants a function
or another class access to private
and protected
members of the class
where the friend
declaration appears.
See here for examples and more on this.
const
at then end of a function
You can think of every class function implicitly being passed
a pointer to the class
instance, like so print(Foo* this)
,
which allows us to access the fields of the instance.
Now, what do you do if you need a class
method where you only
want to be able to read the fields, NOT mutate them? We add a
const
at the end! This can be thought of as print(const Foo* this)
.
class Foo { public: void print() const; }; void Foo::print() const { // do yah stuff }
A good explanation can be found here.
virtual
base class
class A { public: void Foo() {} }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
In the example above, the virtual
keyword makes sure that for class D
the inheritance from class A
corresponds to single "instance" of A
, not
two, as would be the case if we didn't have the virtual
keyword.
So with regular inheritance, you believe you have:
A / \ B C \ / D
But in the memory layout, you have:
A A | | B C \ / D
The virtual
keyword makes sure that you will have the first memory-layout,
as you probably intended. Otherwise, calling a function which is inherited from
A
would be ambigious.
See here for more.
virtual
functions
Simply functions which we allow to be overriden by sub-classes.
Pointers
This is a really good write-up on common practices for using pointers in C++.
Basically, these are the ones which are available to you:
std::unique_ptr
is a smart pointer that owns and manages another object
through a pointer and disposes of that object when the unique_ptr
goes out of scope.
- C++11:
unique_ptr
included in standard - C++14: provided the convenient way of creating new objects:
make_unique<A>()
, instead of having to dounique_ptr<A>(new A())
, as we do below.
#include <iostream> #include <memory> #include <cassert> struct A { A() { std::cout << "Creating A!" << '\n'; }; ~A() { std::cout << "Destructing A!" << '\n'; }; void marco() { std::cout << "Marco!" << '\n'; }; void polo() { std::cout << "Polo!" << '\n'; }; }; std::unique_ptr<A> pass_through(std::unique_ptr<A> a_ptr) { a_ptr->marco(); return a_ptr; } int main() { std::cout << "Unique Ownership Semantics Demo\n"; std::cout << "________________________________\n\n"; { std::unique_ptr<A> p = std::unique_ptr<A>(new A()); // p is a unique_ptr that owns a A auto q = pass_through(std::move(p)); assert(!p); // now p owns nothing and holds a null pointer q->polo(); // and q owns the A object } // ~A called here }
It's worth mentioning that in the above code, the unique_ptr
is actually
constructed / destructed multiple times since we're passing and returning
by copy, not by reference, but the instance of A
lives on until leaving the
scope!
Keywords and specifiers
constexpr
Declares that it is possible to evaluate the value of the function or variable at compile time. This is really useful for things like:
- Constants
- Evaluation of constants, i.e.
const float oneeighty = DegreesToRadians(180.0f)
will be evaluated at compile-time. Since C++ templates do not have something like
trait
in Rust, we cannot be sure that atype
orclass
used in the template actually support the operations used. We really would like to discover such a misuse at compile time. The expression below would make it so that if we callmax
with arguments not supporting the comparison operation, we will get a compile-error instead of runtime-error!template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }
Interesting example
If you compile the code below, then run objdump -C -t <obj_file>
or nm <obj_file> | c++filt
you'll actually see no mention of the arr_size
function!
#include "iostream" // here we're passing an array by reference template <typename T, int N> constexpr T arr_size(T (&array)[N]) { return sizeof(array) / sizeof(array[0]); } int main() { constexpr int arr[] = {-1, 2, 4, -3, 5, 2, -5, 2}; constexpr size_t size = arr_size(arr); std::cout << size << '\n'; }
While if you do the same for the following code:
#include "iostream" template <typename T, int N> T arr_size(T (&array)[N]) { return sizeof(array) / sizeof(array[0]); } int main() { constexpr int arr[] = {-1, 2, 4, -3, 5, 2, -5, 2}; size_t size = arr_size(arr); std::cout << size << '\n'; }
You will find mentioning of the function! This is due to the fact that in the first
example, the compiler computes the size
of the array at compile-time, which means
we can throw away the entire function afterwards (since it isn't used further).
Concepts
Smart pointers
Just a test bro
int N = 10; std::vector<int> v; for (int i = 0; i < N; i++) { v.push_back(i); } for (int i : v) { if (i % 2 == 1) { std::cout << i << std::endl; } else { std::cout << i << ' '; } }
%matplotlib inline import matplotlib.pyplot as plt xs, ys = zip(*t) plt.plot(xs, ys)
Matrix implementation in C++
template <class T> using Vector = std::vector<T>; template <class T> using Matrix = Vector< Vector<T> >; int main() { Matrix<int> A; const int N = 10; for (int i = 0; i < N; i++) { Vector<int> a; for (int j = 0; j < N; j++) { a.push_back(j); } A.push_back(a); } for (Vector<int> row : A) { for (int entry : row) { std::cout << entry << ' '; } std::cout << std::endl; } }
Class impl - better
#include <iostream> #include <vector> #include "tuple" template <class T> using Vector = std::vector<T>; template <class T> using Matrix = Vector< Vector<T> >; template <class T> class VectorClass { public: std::vector<T> data; size_t shape() const { return data.size(); } void print() const; VectorClass<T>& operator+= (const VectorClass<T>& rhs) { if (shape() != rhs.shape()) throw "Vectors do not have same dimension."; for (size_t i = 0; i < data.size(); i++) { this->data[i] = this->data[i] + rhs.data[i]; } return *this; } friend VectorClass<T> operator+ (VectorClass<T> lhs, const VectorClass<T>& rhs) { lhs += rhs; return lhs; } }; /** * Prints the data of the vector. */ template <class T> void VectorClass<T>::print() const { std::cout << "Printing vector" << std::endl; for (T entry : data) { std::cout << entry << ' '; } std::cout << std::endl; } /** * Represents a matrix. */ template <class T> class MatrixClass { private: Vector<size_t> _shape; public: // MatrixClass() { }; MatrixClass(Vector<Vector<T> > data_); MatrixClass(Vector<size_t> shape_); Vector<Vector<T> > data; const Vector<size_t>& shape() const {return _shape; }; void print(); // operator overloadings MatrixClass<T>& operator+= (const MatrixClass<T>& rhs) { if (shape() != rhs.shape()) throw "Matrices not of same shapes."; if (shape().size() < 1) return this; for (auto dim : shape()) { for (size_t i = 0; i < dim; i++) { // Assumes we've already implemented addition for Vector<T> this->data[i] = this->data[i] + rhs.data[i]; } } return *this; } friend MatrixClass<T>& operator+ (MatrixClass<T> lhs, const MatrixClass<T>& rhs) { lhs += rhs; return *lhs; } }; template <class T> MatrixClass<T>::MatrixClass(Vector<Vector<T> > data_) { data = data_; // assign to the property data _shape.push_back(data.size()); if (data.size() > 0) _shape.push_back(data[0].size()); } template <class T> MatrixClass<T>::MatrixClass(Vector<size_t> shape_) { _shape = shape_; // allocate mem for all rows data.reserve(_shape[0]); // allocate mem for each entry on each row if (_shape.size() > 1) { for (int row_idx = 0; row_idx < _shape[0]; row_idx++) { data[row_idx].reserve(_shape[1]); } } } template <class T> void MatrixClass<T>::print() { std::cout << "Printing class" << std::endl; for (Vector<T> row : this->data) { for (T entry : row) { std::cout << entry << ' '; } std::cout << std::endl; } } int main() { const int N = 10; Vector<int> row1 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; Vector<Vector<int> > rows {row1, row1, row1, row1, row1}; auto B = new MatrixClass<int>(rows); B->print(); auto a = new VectorClass<int>(); a->data = row1; auto b = new VectorClass<int>(); b->data = row1; auto c = *a + *b; c.print(); }