Classes are blueprint that allow us to define abstractions.
Objects are specific instances created from a class.
They are the building blocks of C++.
Say you want to make a lot of triangles, you create a `Triangle` class that serves as the assembly line for triangles. You give the assembly line what it needs to build a triangle, and you get *a* triangle in return, which we can call an *instance* of the class `Triangle`.
The example below creates an *instance* called `my_triangle` of the *class* `Triangle`.
```cpp
int main()
{
Triangle my_triangle;
return 0;
}
```
Object-Oriented Programming, the paradigm embraced by C++, makes it easy to write clear, maintainable and reusable code.
Say, for instance, you're writing a program to render a 3D model onto a 2D screen. You need to apply a sequence of linear and projective transforms to do so. Creating classes such as `World`, `Camera`,`Object` makes reasoning about your program much more intuitive. Logic becomes self-contained in the classes that implement them, and you can build your way up from simplicity to complexity, while keeping your code organized.
Those classes would then define the attributes and methods they need in order to function.
## Definition
```cpp
class Rectangle
{};
```
## Instantiation
```cpp
class Rectangle
{};
int main()
{
Rectangle plane;
return 0;
}
```
## Members
### Member Attributes
#### Declaration
```cpp
class Rectangle
{
double height;
double width;
};
```
Attributes are `private` by default, which means they are not accessible by an instance of the class. To make the separation clearer between public and private attributes, it's best to mark them as such explicitly.
```cpp
class Rectangle
{
private:
double height;
double width;
};
```
Creating an instance `place` of the above class `Rectangle`, then trying to access or set `plane.height` would yield an error.
If you wish to enable consumers of the class's API to access or set attributes it relies on, it's best to use getters and setters. They are intermediary methods that allow consumers to manipulate attributes under the control and the restrictions that make sense for the class. This way you mitigate the risk for undefined behavior.
To make an attribute or a method public, they need to be placed under the public keyword.
```cpp
class Rectangle
{
public:
double surface()
{}
private:
double height;
double width;
};
```
### Member Methods
```cpp
class Rectangle
{
public:
double surface()
{
return height * width;
}
private:
double height;
double width;
};
```
Methods can use the class's attributes directly, whether private or public.
The signature of a method is the same as that of a function.
It's common practice to separate method declaration from method implementation.
Methods usually get implemented in a `MyClass.cpp` file, while they get declared in a `MyClass.h` headers file.
#### Class Headers
Splitting our code like so makes it easier to navigate and maintain.
```cpp
// Rectangle.h
class Rectangle
{
public:
double surface();
void setHeight();
void setWidth();
private:
double height;
double width;
};
```
```cpp
// Rectangle.cpp
#include "Rectangle.h"
void Rectangle::setHeight(double height)
{
this->height = height
}
void Rectangle::setWidth(double width)
{
this->width = width
}
double Rectangle::surface()
{
return width * height;
return 0;
}
```
```cpp
// main.cpp
#include <iostream>
#include "Rectangle.h"
int main()
{
Rectangle plane;
plane.setHeight(10);
plane.setWidth(10);
double surface = plane.surface();
std::cout << surface << std::endl; // Prints 100 to the console
return 0;
}
```
>👉 `#include` statements are what allows us to use code from other files and libraries as if they were in the same file where you use them. More technically, they tell the preprocessor to copy-paste the header's content into the file, so that the linker connects the compiled code later in the process.
#### Getters & Setters
```cpp
class Rectangle
{
public:
double surface()
{
return height * width;
}
double getHeight()
{
return height;
}
void setHeight(double h)
{
height = h;
}
double getWidth()
{
return width;
}
void setWidth(double w)
{
width = w;
}
private:
double height;
double width;
};
```
#### Variable Shadowing
In the above example you see the setter methods had to name their parameters `w` and `h`, in order to escape variable shadowing. The following code would resulted in an error, because the attribute name is shadowed by that of the parameter in the inner scope of the function.
```cpp
```cpp
class Rectangle
{
public:
// ...
void setHeight(double height)
{
height = height;
}
// ...
void setWidth(double width)
{
width = width;
}
// ...
};
```
Having many names for the same thing isn't the neatest thing however, and C++ provides us with a way to circumvent that. Let's fix the above code.
```cpp
```cpp
class Rectangle
{
public:
// ...
void setHeight(double height)
{
this->height = height;
}
// ...
void setWidth(double width)
{
this->width = width;
}
// ...
};
```
>👉 The `this` keyword is a pointer to the current instance, effectively allowing us to bypass variable shadowing, thus fixing our program.
>👉 The arrow syntax `A->B` dereferences the pointer. It is equivalent to `(*A).B`.
However, it's best to avoid dealing with such issues altogether.
A common practice in C++ is to differentiate attributes by putting the underscore character `_` at the end of their name.
```cpp
class Rectangle
{
private:
double height_;
double width_;
};
```
#### Mutators & Accessors
Getters and setters, which we discussed earlier, are really a specific case of a more general pattern. In C++, we differentiate methods that mutate an instance's state from those that don't using the `const` keyword.
```cpp
// Rectangle.h
class Rectangle
{
public:
double surface() const;
double getHeight() const;
void setHeight(double h);
// ...
private:
double height;
double width;
};
```
This prevents accidental modifications of the instance's state by making a clear distinction between the methods that mutate it and those that don't.
>👉 A method marked `const` can only call other methods that are marked const. You can see how it would otherwise be meaningless to have this keyword.
Additionally, if you create an instance and mark it as a const, you can only ever call its member functions (accessor methods) that are also marked as `const`. Let's consider the following example that uses the class defined above.
```cpp
// main.cpp
int main()
{
const Rectangle plane;
plane.setHeight(10); // 🚫 compile-time error
return 0;
}
```
#### Implicit Casts
If you want to make sure a type doesn't get casted into another when calling a method, you can `delete` this type from the function signature.
For instance, an `int` can be fitted into a `double`. We could prevent is like follow:
```cpp
class Demo
{
public:
double my_fn(double x) {}
double my_fn(int x) = delete;
};
```
### Static Class Members
Let's say we want to keep track of the number of active instances of a given class.
We *could* create a global variable. This is hardly maintainable and prone to undefined behavior.
The right tool for the task would be to use a `static` member in our class definition.
```cpp
class Item
{
public:
Item(){
// Increment count when we create a new instance
++count
}
~Item(){
// Decrement count an instance gets destroyed
--count
}
static int count; // The declaration does not initialize count
};
int Item::count; // The definition zero-initializes static members
```
When we mark `count` as `static`, we ensure that no matter the number of objects created, there will only ever be one copy of this variable.
In other words, we use static members to create shared resources between instances of a class.
#### Accessing Static Members
To access data from a static member, you can (and should) do so using the class name itself and the scope resolution operator `::`. The syntax is `MyClass::static_member`.
It's possible to access `static` members through one of the class instances with something along the lines of `my_instance.static_member`, but it's anti-pattern to do so.
Let's now see how we'd use the `Item` class and its `static` member from the example above.
```cpp
#include "Item.h"
void print_count()
{
std::cout << Item::count << std::endl;
}
int main()
{
Item item_1;
print_count(); // yield 1
if (true)
{
Item item_2;
print_count(); // yield 2
}
print_count(); // yield 1
Item item_3;
print_count(); // yield 2
return 0;
}
```
#### Static Member Initialization
Class attributes are typically initialized when an instance is created. However, `static` members are *not* specific to any given instance, and can be access even if no instance of the class exists.
It stands to reason that we cannot initialize them alongside with other attributes, since `static` members exist even if no instance of the class exists. Also, for our given example, that would effectively reset our counter with each and every instance creation, defeating the purpose of what we were trying to achieve.
We need to initialize them outside the class.
```cpp
class Rectangle { /* ... */ };
int Rectangle::count(0);
```
By default, static members are initialized to zero when they're defined (not declared).
We could have zero-initialized writing only `int Rectangle::count;`. But we can't skip the definition.
#### Constant Static Members
In practice, this pattern is often used to define **shared immutable resources** between instances.
This could be the retirement age for an employee registry, tax brackets for a tax resident, the max temperature a sensor can reach before triggering a warning, etc.
```cpp
class Sensor
{
private:
static const int MAX_TEMP;
static const double PI;
}
const int Sensor::MAX_TEMP(33);
const double Sensor::PI(3); // Because why bother
```
C++ 17 brings more flexibility with the `inline` keyword. That way you can define shared immutable resources in the class definition itself. I think it's neat to be able to understand a class's state without leaving its headers.
Let's revisit the above example.
```cpp
class Sensor
{
private:
inline static const int MAX_TEMP = 33;
}
```
#### Static Methods
As exposed in this sections, all members of a class can be made `static`, including methods.
This pattern is less present in Object Oriented Programming, while possible using the syntax below.
```cpp
static int get_count()
{
return count;
}
```
Just like with static attributes, invocation would use the class name itself: `Item::get_count()`.
Those methods also can be invoked regardless of whether there are any instances of the class.
They cannot access any other members that presuppose the existence of `this`; they are restricted to other `static` members of the class.
Maybe that makes sense for methods that are designed to access or mutate shared resources.
In C++, **namespaces** are generally preferred to **utility classes** if what you're trying to achieve is wrap functions in a specific context.[^1]
## Constructor & Initialization
There's a major problem with our `Rectangle` class so far. Its attribute are not initialized when we create the object, which means they hold random garbage values, which is a source of major bugs.
We *could* rely on setters for each attribute of our class.
However, relying on the use of setter methods by the consumer is *anti-pattern*. A class instance will be able to live for an unknown period of time with those random garbage values, which can create bugs in the consumer program.
Plus, relying on consumers to adapt their code to the internals of our class violates the principles of encapsulation that we'd like to follow to make collaboration more effective. Bad choices like that, however small, have a tendency to compound over time and to make a system more and more fragile instead of making it more robust by the day.
We *could* define an `init` method that would take care of instance initialization. This would be neater, as we would be initializing the object as intended.
Yet again, this would require additional effort from the consumer to worry about the API of each and every class he uses, thus not achieving an efficient separation of concerns. What would be really neat is to make it impossible for the user to use our class without a proper initialization.
The solution, as spoiled by the title, is to rely on C++ built-in constructors.
Actually, we've been using constructors for some time already.
All classes have a compiler-generated default constructor. This is the constructor that gets called when you haven't defined a default constructor yourself. This is the constructor that initializes attributes with random garbage values when you could not be bothered with defining how your class should be initialized. If those attributes are objects, however, they get instantiated with their default (default?) constructor.
Constructors are called automatically when an instance is created, and requires no additional action from the consumer. The compiler will yield an error if the instance is not built appropriately, preventing our program to be compiled with the bugs that could come from this misuse.
To create a constructor, you merely need to declare a method sharing the class's name.
```cpp
class Rectangle
{
public:
Rectangle()
{}
};
```
This example does the exact same thing as the compiler-generated default constructor that's added by the compiler if you fail to provide one.
>👉 You should not specify a return type for constructors; this is by design.
Let's now see how we can improve it to fit the constraints of our class.
### Constructor's Parameters
First and foremost, the constructor can accept parameters, which you can use to set the values of the instance's attribute using the setter pattern we learned earlier.
```cpp
class Rectangle
{
public:
Rectangle(double height, double width)
{
height_ = height;
width_ = width;
}
private:
double height_;
double width_;
};
```
You could then pass the parameters to this constructor by creating an instance like below.
```cpp
int main()
{
Rectangle plane(10.0, 15.0);
return 0;
}
```
This make the initialization pattern we discussed earlier with setters somewhat obsolete.
Setters are still useful, though, as it is not rare for real-world programs to involve state updates and the ability to react to those state changes during an instance's lifetime.
>👉 Note that you can move the constructor implementation to `Rectangle.cpp`, using the same syntax as before: `Rectangle::Rectangle() {}`. However, I feel it best to initialize state within the class definition, where the attributes are declared. This makes it clear for other maintainers or curious consumers what values can or cannot be expected throughout the instance's lifetime.
### Initializing Instance-Attributes
The above syntax has its limits. Consider the following example.
```cpp
class Rectangle
{
public:
Rectangle(double height, double width, string hex_color)
{
height_ = height;
width_ = width;
color_ = Color(hex_color)
}
private:
Color color_;
double height_;
double width_;
};
```
Here we instantiate the object `color_`, an instance of the class `Color`, within the `Rectangle` class.
The way we create the Color `instance`, however, is problematic. Before we enter the body of the constructor, `color_` is default constructed, alongside with `height_` and `width_`, which hold random garbage values at that point. Once we enter the body, we reassign `height_` and `width_`, before creating a temporary `Color` object that then gets copied into `color_`.
Now, imagine working with a graph made of tens of millions of nodes, and ponder how many resources would be wasted with all those useless memory allocations.
Long story short, we want to do better.
This is where the **initialization list** comes in.
```cpp
class Color
{
public:
Color(string hex_color)
: hex_value_(hex_color)
{}
private:
std::string hex_value_;
};
class Rectangle
{
public:
// initialization list, we remove the constructor's body
Rectangle(double height, double width, std::string hex_color)
: height_(height), width_(width), color_(hex_color)
{}
private:
Color color_;
double height_;
double width_;
};
```
This performs a **direct initialization**, calling the `Color` constructor exactly once during the `Rectangle` instantiation, with no intermediate default construction or assignment.
With the initialization list pattern, the constructor calls the appropriate constructor based on the type of the attribute, and there are no more wasteful memory allocations.
This is the preferred pattern to build instances.
>👉 It's possible to combine operations in the body of the constructor with an initialization list, of course. However, postponing initialization to happen in the body means there is some time during which attributes will hold random garbage values. A good rule of thumb is to move as much as possible to the initialization list, but no more.
### Constructor Overloading
C++ functions can be overloaded.
This means that functions having the exact same name can coexist, provided their *signatures* are different. The compiler will match the function calls with the corresponding function based on the type of the arguments passed to the function.
This way, we can provide various ways of creating instances of a class for its consumers.
```cpp
class Rectangle
{
public:
Rectangle(): height_(1.0), width_(1.0) {}
Rectangle(double length): height_(length), width_(length) {}
Rectangle(double height, double width): height_(height), width_(width) {}
private:
double width_;
double height_;
};
```
Let's now consider the following instantiations of our class `Rectangle`.
```cpp
int main()
{
Rectangle plane; // Calls the first constructor
Rectangle square(10.0); // Calls the second constructor
Rectangle surface(12.4, 34.3); // Calls the third constructor
return 0;
}
```
### Default Constructors
Thanks to overloading, we can have as many default constructors as we need.
#### Compiler-Generated Default Constructor
Remember, classes have a compiler-generated default constructor, a default default constructor if you will. This is the constructor that gets called when you haven't defined a default constructor yourself. It initializes attributes with random garbage values. If those attributes are objects, however, they get instantiated with their default (default?) constructor.
Once you've defined a constructor (default or not), however, the *default default* one ceases to exist.
Since C++ 11, however, if you still need the default constructor after defining a default constructor, you can ask it back by adding `MyClass() = default;` to your constructors (see example below). It would make sense, for instance, in the case where a class only has objects as attributes, and you want those to be instantiated with their default constructor without passing any arguments.
#### Default Constructor
Default constructors, beyond that which is automatically added by the compiler when there are none, are constructors that have no parameters or have default values for all their parameters.
Default values can be provided by a constructor; effectively this allows us to merge two constructors into one, and instances will be created based on whether or not they provide arguments at initialization time.
Below, we merge the first two constructors we had before into one.
```cpp
class Rectangle
{
public:
Rectangle() = default;
Rectangle(double length = 1.0): height_(length), width_(length) {}
Rectangle(double height, double width): height_(height), width_(width) {}
private:
double width_;
double height_;
};
```
It will then be possible to invoke this constructor without providing any arguments with the familiar syntax `Rectangle plane`.
>🚫 Since default constructors provide defaults, this is the only way to call a default constructor. You cannot call it with `Rectangle plane()`. The compiler won't throw an error right away though. This is interpreted as a function call without parameters that returns a `Rectangle`. This is the case even if the constructor provides default values.
#### Default Constructor Deletion
If you need to prevent a consumer from creating an instance of a class without passing it arguments, you can delete the default default constructor.
This has little application for classes, since it disappears as soon as you define any other constructor.
It makes more sense for **structs**, though, which are functionally identical to classes, with one major difference being that members of a struct are public by default.
You would use a struct to implement types such as a `Point` that has `x` and `y` coordinates but doesn't have the need for any methods.
Below, we define a struct `Foo`where there is no default constructor. This prevents the use of this class without initializing the struct.
```cpp
struct Foo
{
Foo() = delete;
int i;
};
```
We'll discuss structs extensively at a later point.
#### Constructor Delegation
Constructors can call each other, too.
This is a good way to provide defaults while making your code a bit more readable.
Let's rewrite the example we had above.
```cpp
class Rectangle
{
public:
Rectangle(double height, double width): height_(height), width_(width) {}
Rectangle(): Rectangle(1.0, 1.0) {}
private:
double width_;
double height_;
};
```
#### Default Attribute Values
It's also possible to initialize attributes where they are declared.
```cpp
class Rectangle {
// ...
private:
double width_ = 0.0;
double height_ = 0.0;
};
```
What applied when we were discussing *anonymous instances* getting copied around when initializing objects in the body of the constructor still applies here, and you should not instantiate objects like that.
Modern C++ advice deems it best to provide a default value at the point of declaration like this. This acts as a safety net, effectively guaranteeing a member is never uninitialized, even if a new constructor gets added later on and someone forgets to add it to the initializer list.
It does not result in more operations, since the constructor's initializer list will override this default if it specifies a different value.
This practice makes classes more robust and less error-prone.
### Implicit Conversions
Constructors can perform implicit conversions on behalf of the user. Consider the following example.
```cpp
class MyNumber
{
public:
MyNumber(int value): value_(value)
{}
private:
int value_;
};
void printNumber(MyNumber num) { /* ... */ }
int main()
{
printNumber(42); // silent conversion into MyNumber
return 0;
}
```
In the above example, the number `42` gets converted silently into an instance of `MyNumber`.
This can lead to surprising bugs, and it can be a good thing to ask of the consumer that he provides exactly the types the constructor expects.
We use the `explicit` keyword to make our code safer.
```cpp
class MyNumber
{
public:
MyNumber(int value): value_(value)
{}
private:
int value_;
};
void printNumber(MyNumber num) { /* ... */ }
int main()
{
printNumber(42); // 🚫 compile-time error
// You must perform an explicit conversion
printNumber(MyNumber(42));
printNumber(static_cast<MyNumber>(42));
return 0;
}
```
## Copy Constructor
A copy constructor is a constructor whose signature accepts an instance of the same class.
A compiler-generated default version of this constructor exists where none is provided. You only need to write it in rare situations such as when a class *must* hold onto a raw pointer.
If you need to write it, the golden rule is to define all of the 'big three' constructors together: copy constructor, copy assignment operator and destructor.
We can create copies of an object by passing an instance as an argument to the class constructor. The copy constructor is also called when we pass arguments to a function by value.
```cpp
Rectangle r1(12.4, 23.1);
Rectangle r2(r1); // calls the copy constructor
bool result = my_function(r2); // calls the copy constructor
```
This performs a shallow copy, known in C++ as a **member-wise copy**, iterating through each member variable (attribute) of the the object being copied. If the value is a simple type, like a `double`, it gets copied. If a member is an instance of another class, this other class's own constructor is called in turn to create a separate copy.
This can get tricky when the class manages a raw pointer, though. In this case, the raw pointer's memory address gets copied from one instance to its copy. To solve this, you need to create a [[Deep Copies|deep copy]].
In the example above, we passed an object by value to a function, which called the copy constructor. There is some overhead to this, as memory needs to be allocated and the copy constructor is called.
So it might make sense in some situations to pass arguments by *constant reference* (since we don't manipulate the received instance) to avoid this memory allocation overhead.
```cpp
Rectangle(const Rectangle& other): Rectangle(other.height, other.width) {}
```
Here, we leverage constructor delegation in the definition, calling the class constructor in the initialization list of the copy constructor.
Note, however, there's already a good deal of optimization the compiler can achieve on its own. There might be cases where passing by reference might prevent those optimizations, resulting in slower execution and higher bundle size.[^2]
If you wish to make copying an object impossible, like with every other constructor, you can `delete` it from the class.
```cpp
class Rectangle
{
// ...
Rectangle(const Rectangle&) = delete;
// ...
};
```
This class can no longer be copied.
This is useful, for instance, if you created a class that holds many objects like a physics simulator. Copying such an object would painfully slow down your program. Deleting the copy constructor makes sure the compiler will yield an error if you inadvertently pass this object by value instead of passing it by reference.
## Destructors
When an object mobilizes resources, it's important that we free them resources after usage.
Imagine that we had written the `Rectangle` class to hold onto pointers towards `double` values rather than the values themselves.
```cpp
class Rectangle
{
public:
Rectangle(double* height, double* width): height(height), width(width) {}
Rectangle(): Rectangle(new double(2.0), new double (3.0)) {}
private:
double* height;
double* width;
};
int main()
{
if(true) {
Rectangle r;
}
return 0;
}
```
The `new` keyword is used to allocate memory for an object at runtime.
When we create an instance of the class `Rectangle`, we get an object whose attributes are two pointers to memory dynamically allocated by the constructor at runtime.
When execution is passed the scope of the `if` statement, the variable `r` in which it was defined exists no more. But the memory reserved for the values `height` and `width`stays allocated.
For every use of the keyword `new`, we must have a corresponding use of the keyword `delete` to deallocate unused memory.
We could, of course, implement this as a method and ask of the consumer that he remembers to free the allocated memory after usage. Or worse, make our attributes public and ask of him that he uses the `delete` keyword himself.
This would make encapsulation weaker and is error-prone. The rule is that it's the responsibility of the person who allocated memory to free it as well.
To tackle this problem, C++ offers a method called a destructor that is automatically invoked when the lifecycle of the instance reaches its end, where we can free all the kinds of resources the instance might have been using.
Defining a destructor follows the same syntax as that of the compiler-generated default constructor, with the class name prefixed with `~`.
```cpp
class Rectangle
{
public:
Rectangle(double* height, double* width): height(height), width(width) {}
Rectangle(): Rectangle(new double(2.0), new double (3.0)) {}
~Rectangle()
{
delete height;
delete width;
}
private:
double* height;
double* width;
};
int main()
{
if(true) {
Rectangle r;
}
return 0;
}
```
Using the keyword `delete` frees the memory as soon as the object gets deleted, so that it can be garbage collected. If we had allocated memory using `std::malloc`, we would have used `std::free` to deallocate it.
Obviously, this destructor doesn't have any parameters, which implies it cannot be overloaded and there can only be one.
If a destructor isn't defined, a minimalistic version gets automatically generated by the compiler.
Note that the above example was provided to illustrate our point. Without those dynamical memory allocations it would have been pointless to write a destructor, since the one generated by the compiler would have been more than sufficient.
It should under no circumstances serve as a reference.
[^1]: [Are utility classes with nothing but static members an anti-pattern in C++?](https://softwareengineering.stackexchange.com/questions/134540/are-utility-classes-with-nothing-but-static-members-an-anti-pattern-in-c)
[^2]: [Want Speed? Pass by Value](https://www.scribd.com/document/316704010/Want-Speed-Pass-by-Value) by David Abrahams