University of Leeds

Enums and Structs

Introduction

We will now look at some useful C++ language features and look at some examples of how they can be used.

Enum

An enum, or enumerated type, is a data type from the C language that consists of a group of elements as defined by the programmer. Each of these types is represented by an integer value. The first value is equivalent to 0, the second equal to 1 and so on. They can be useful for making code more readable. For instance, you may want to represent different fruit in your code. You could do this using an int, and give a value 0 to an apple, a value 1 to a banana and so on. However, you may forget the values for each fruit and it could be hard for another developer working on your code to follow. Instead you could create an enum like so

main.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <iostream>

// constants in the list are assigned integers: 0,1,2 etc.
enum Vegetable { Potato, Carrot, Onion };
enum Fruit { Apple, Banana, Pear };

int main() {
  // create enum types and assign values (both are 0)
  Vegetable veg = Potato;
  Fruit fruit = Apple;
  // we can directly compare as they are essentially integers
  if (fruit == veg) {
    std::cout << "The types are the same!\n";
  }
  return 0;
}

We can now create a variable of type 'Fruit' and assign it the values 'Apple', 'Banana' and 'Pear' which have the integer values 0, 1 and 2 respectively. This makes the code much more readable.

However, this code also demonstrates a potential problem when using enums. We have also created an enum for Vegetables. We then use the equivalence operator to check whether an apple is the same as a potato. Since they are both enums and in this case, both have the binary value 0, the operation will return true! In the eyes of the code, it considers an apple and potato to be equivalent. This could cause problems, imagine a recipe app getting potatoes and apples mixed up. I certainly would not like to eat potato crumble and custard! Or a jacket apple with beans and cheese!

Enum Class

We can avoid potential problems with using enums by using enum classes. These were introduced in C++11 and are very similar to standard enum's. The main difference is that the enum values are local to the enum class and cannot be assigned to other data-types. The above example re-written using enum classes is shown below.

main.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>

enum class Vegetable { Potato, Carrot, Onion };
enum class Fruit { Apple, Banana, Pear };

int main() {
  // create enum class types
  Vegetable veg = Vegetable::Potato;
  Fruit fruit = Fruit::Apple;

  // this causes a compile error as the types are now different
  if (fruit == veg) {
    std::cout << "The types are the same!\n";
  }

  return 0;
}

If you try and compile the above code, you will now get an error, preventing from bugs creeping into your code. Notice how similar code is, we just need to add the class keyword when creating the enum class and then use the scope operator :: to tell the compiler which enum class the value belongs to.

You should use enum classes in your code rather than enum.

Struct

Another useful language feature is a struct. A struct allows us to create a custom data structure. We can essentially group together multiple variables of different types (e.g. int, float, string) into a single structure. These are especially useful for returning multiple (related) values from a function. We know that in C/C++ we can only return a single variable using a return statement. Suppose we have a function for an embedded system that calculates the x,y and z component of acceleration using an accelerometer. Rather than having separate functions to get the x,y and z components individually, you can just have one function and group the x,y and z components together into a single structure representing a 3D vector.

The example below shows how to define a structure to represent a two-dimensional point (i.e. an x and a y co-ordinate). It also shows the various ways of initialising and assigning values. Note that some methods are only possible in C++11 and newer.

main.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

struct Point2D {
  int x;
  int y;
};

int main() {
  // create a 2D point struct and initialise using an initialiser list
  Point2D origin = {0, 0};
  // in C++11, we can also initialise using uniform initialisation
  Point2D destination{45, 76};
  // we can also create an 'empty' struct
  Point2D corner;
  // prior to C++11, we would have to assign values to variables individually
  corner.x = 10;
  corner.y = 5;
  // since C++11, we can assign values using a list
  corner = {12, 15};
  return 0;
}

Struct vs. Class

In C++, a struct is actually very similar to a class. In the last example, we just saw a struct with member variables, but it is actually possible to add methods to a struct. Imagine a struct to represent a vector that contains an x and y component. We could easily add a method to get the magnitude of the vector. Therefore the struct will have member variables and member methods...sounds very similar to a class! The only real difference is that in a struct the members are public by default, whereas with a class, we would have to use the public keyword. The code below implements the same functionality using a class and a struct. Note how similar the code is.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <cmath>
#include <iostream>

// in a struct, the members are public by default
struct Vector {
  int x;
  int y;
  // magnitude of the vector
  int mag() { return sqrt(x * x + y * y); }
};
// in a class, the members are private by default
class VectorClass {
 public:
  int x;
  int y;
  // magnitude of the vector
  int mag() { return sqrt(x * x + y * y); }
};

int main() {
  // Example using a struct
  Vector vec = {3, 4};
  int mag = vec.mag();
  std::cout << "Magnitude of {" << vec.x << ", " << vec.y << "} = " << mag
            << std::endl;
  // Example using a class
  VectorClass vec_class = {3, 4};
  int mag_class = vec_class.mag();
  std::cout << "Magnitude of {" << vec_class.x << ", " << vec_class.y
            << "} = " << mag_class << std::endl;
  return 0;
}

Despite the almost interchangeable nature of classes and structs, the most common convention appears to be the use of structs for grouping together variables, without any methods (sometimes called 'plain-old data' or POD). Classes tend to be used when there are member methods as well as member variables. It is recommended that you stick to this convention.