Two Weeks of C++ - Day 02

Intro

This is day 02 of the Two weeks of C++ series. I normally post every two days, but I’ve been more focused on my mobile app recently (React Native). Now I’m back to keeping it 50/50 :) Let’s keep going.

Today I’m learning about user-defined types, which are types built out of the built-in types using C++’s abstraction mechanisms.

The standard library of C++ mainly consists of user-defined types.

User-Defined Types

The new operator and the heap

I also point out stuffs not related to the current subject. I admit, it’s not smart, but since those are personal notes, I know where to find them ;).

The new operator allocates memory from an area called free store. Always destroy, using delete, memory allocated with new to avoid memory leaks. Just like malloc and free in C.

The free store == dynamic memory == heap.

Structures

Depending on a function declaration and how you pass a structure to it, here’s how to access the structure’s elements:

1
2
3
4
5
6
void f(Vector v, Vector& rv, Vector* pv)
{
int i1 = v.sz; // through a name
int i2 = rv.sz; // through a reference
int i3 = pv->sz; // through a pointer
}

Remember I said I would talk about using an argument through reference in the last post? Note that we’re not dereferencing rv to be able to access its sz element or any other possible elements whatsoever.

However, we would need to dereference pv, because it’s a pointer. In this example, it’s a struct, hence the -> notation.

That’s how C++ works, keep this in mind.

Classes

Classes allow to distinguish between the interface to a type and its representation/implementation. The interface is defined by the public members and the representation - the private members - is only accessible through this interface.

1
2
3
4
5
6
7
8
9
class Vector {
private:
double* elem; // Pointer to the elements
int sz; // the number of elements
public:
Vector(int s) :elem{new double[s]}, sz{s} {} // The constructor
double& operator[](int i) { return elem[i]; } // Element access through the subscript operator
int size() { return sz; } // Element access through a method
};

NOTE: It’s always good to initialize the elements of your class in the same order you declared them because the compiler will always initialize the elements based on the order they were declared in the class, whether you like it or not.

The reason is, normally, when you destroy allocated memories, you use a reverse order method so that you really don’t miss any of them, in case a variable on top depends on one below.

Well every class can have a destructor that uses the same method. So to ensure that properties were always destroyed in the correct reverse order, the simplest solution was to choose the order of declaration over the order of initialization.

It’s not an error when you don’t do it that way, it’s just a warning when you use the -Wall flag in your compilation command.

Here’s how we would initialize and use our Vector class:

1
2
3
4
5
6
7
8
9
10
11
double read_and_sum(int s)
{
Vector v(s); // Initialize an object
for (int i = 0; i < v.size(); ++i)
std::cin >> v[i]; // Read into each elements
double sum = 0;
for (int i = 0; i < v.size(); ++i)
sum += v[i]; // Take the sum of all elements
return sum;
}

Notice how we can access the vector’s elements through an array-like notation? That’s the power of subscripting operator overloading.

Unions

Nothing new about unions really, except for this great advice:

It’s good to encapsulate a union and its type field in a structure/class to ensure they correspond to each other instead of plainly using them in the open. Here’s an example of a good way to do it:

1
2
3
4
5
6
7
8
9
10
enum Type { str, num };
union Value {
char *s;
int i;
}
struct Entry {
char* name;
Type t;
Value v; // uses v.s if t==str, uses v.i if t==num
}

This is a well known advice among 42 students. For some reason I decided to put it here anyway. Maybe this chapter was too short :P

Enumerations

On top of the basic enum that we already know, C++ provides scoped enums. I gotta say, I love it!

It’s really just an enum improved with some class features like operators. Here’s a basic example:

1
2
3
4
5
6
7
8
enum class Traffic_light { green, yellow, red };
enum class Color { red, green, blue };
Color c = Color::red; // OK
int c2 = Color::blue; // KO, different types
Traffic_Light t = Color::blue; // KO, different types
Color c1 = 1; // KO, different types
Color c4 = red; // KO, which red? Doesn't exist

As you can see, unlike basic enums, the type of a scoped enum is not interchangeable with an int type. This prevents accidental misuses of constants. Also, you must use the namespace of the enum to be able to use it.

Here’s one way to create an operator for scoped enums:

1
2
3
4
5
6
7
8
9
Traffic_light& operator++(Traffic_light& t)
{
switch (t)
{
case Traffic_light::green: return t=Traffic_light::yellow;
case Traffic_light::yellow: return t=Traffic_light::red;
case Traffic_light::red: return Traffic_light::green;
}
}

Isn’t this beautiful? Could be even better with templates but that’s for later. Here’s what a basic usage of this would be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;
enum class Traffic_light { green, yellow, red };
Traffic_light& operator++(Traffic_light& t)
{
switch (t)
{
case Traffic_light::green: return t=Traffic_light::yellow;
case Traffic_light::yellow: return t=Traffic_light::red;
case Traffic_light::red: return t=Traffic_light::green;
}
}
int main(void)
{
Traffic_light light = Traffic_light::green;
Traffic_light next = ++light;
cout << static_cast<underlying_type<Traffic_light>::type>(next) << '\n';
}

Notice how we had to explicitly cast it before printing it out? Unlike a basic enum, a scoped enum is not implicitly convertible to its integer value.

Conclusion

You’ll notice I don’t go too deep into some new terms, like subscripting operator overloading in classes. That’s because they are coming in day 04, so for now, look them up to be ready for them later.

Here’s what to remember for the day

  • Organize related data into structures (structs or classes)
  • Represent the distinction between an interface and an implementation using a class
  • A struct is simply a class with its members public by default
  • Define constructors to guarantee and simplify initialization of classes
  • Avoid naked unions; wrap them in a class/struct together with a type field
  • Prefer class enums over basic enums to minimize surprises
  • Define operations on enums for safe and simple use