Part 5 - classes done right

Let's start with definition. What is a class? A class is a struct which fields are private by default. Compared to C it also can have methods (typical OOP stuff).

class Example
{
// this field is private
int a;

public:
// this is now public
int b;

protected:
// protected :)
int c;

private:
// back to private
int d;
};

methods

Underneath methods are just regular functions that add pointer to object as 1st argument. The following example shows just that:

class Example
{
void do_something(int a);
};

void do_something(Example& this, int a);

Methods marked as const cannot modify object state and must be threadsafe.

class Example
{
void do_something(int a) const;
};

void do_something(const Example& this, int a);

constructors

Constructor is a special method that is invoked when creating object of a class. Every class/struct gets one, unless you specify a constructor by yourself. Before constructor is called, memory of entire object is allocated. If your constructor taks only 1 argument, you should ususally mark it as explicit.

class Example
{
explicit Example(int arg)
: data(arg)
{
std::cout << "here << "\n";
}

int data;
};

auto a = Example{};

member initializer list

It's better to use member initializer list instead of manually assigning values in ctor, as this way it's actually faster.

class Example
{
// BAD
Example(int arg)
{
this->data = arg;
};

// GOOD
Example(int arg)
: data(arg)
{};

int data;
};

destructor

Destructor is a special method invoked when object is destroyed. If you declare a custom destructor you loose default copy/move constructors as well as copy/move assignment operators. To get them back you need to define them by yourself or declare them as default.

class Example
{
~Example();

// use default
Example(const Example& other) = default;
};

deleting methods

You can delete any method you want. For example, if you don't want class to be copied you can delete copy constructor and copy assignment operator. This will prevent any calls to those functions during compiletime.

class Example
{
Example(const Example& other) = delete;
Example& operator=(const Example& other) = delete;
};

overloading operators

You can overload operators when it intuitively makes sense to (usually math stuff like vectors). In 99.9% of the time you shouldn't do it. I'm looking at you - stdlib and random HTTP server libraries and stdlib that abuse operators for no reason.

Exception to that are copy/move assignment operators.

// constructor called
auto a = Example{};

// copy constructor called
auto b = Example{a};

// move constructor called
auto c = Example{std::move(b)};

// copy assignment operator called
auto d = c;

// move assignment operator called
auto e = std::move(d);

rule of 0

Classes that have custom destructors, copy/move constructors or copy/move assignment operators should deal with ownership. Other classes should not have those features.

class rule_of_zero
{
public:
rule_of_zero(int arg)
: data(arg)
{};

private:
int data;
};

rule of 5

If a class requires a user-defined destructor, copy/move constructor or copy/move assignment operator it almost certainly requires all 5.

class rule_of_five
{
public:
rule_of_five(const char* arg = "")
{
if (arg)
{
auto n = std::strlen(arg) + 1;
this->data = new char[n];
std::memcpy(data, arg, n);
}
}

~rule_of_five()
{
delete[] data;
}

rule_of_five(const rule_of_five& other)
: rule_of_five(other.data);
{}

// https://en.cppreference.com/w/cpp/utility/exchange
rule_of_five(rule_of_five&& other) noexcept
: data(std::exchange(other.data, nullptr))
{}

rule_of_five& operator=(const rule_of_five& other)
{
return *this = rule_of_five(other);
}

rule_of_five& operator=(rule_of_five&& other) noexcept
{
std::swap(data, other.data);
return *this;
}

private:
char* data;
};

friend keyword

Read more in part 10.

Mastodon