C++ Enum Pattern
Introduction
Very often we want to define a set of items to choose from, for example, a set
of colors. In C++, we usually can declare enum class Color { Red, Green, Blue
}
. However, besides using it in a switch
statement, we can hardly use it for
anything else. For example, if we want to print "Red"
for Color::Red
, we have to
write another function using switch
statement, somewhere else. When we
want to add a new color, we have to change every places we are using switch
.
This is obviously an anti-pattern. Occasionally, we want to find how many colors in total,
and maybe even want to iterate through them. None of these are supported by C++
enum
. In this blog, I want to introduce the Enum Pattern in C++, which
supports switch
and constains encapsulated member functions, similar to the Enum Class
in Java. The idea came from this stackoverflow
post.
The Code
class GuitarBrand { // Enum Class Pattern
public:
static const GuitarBrand FENDER;
static const GuitarBrand GIBSON;
static const GuitarBrand PRS;
// If we want to add another GuitarBrand Ibanez:
static const GuitarBrand IBANEZ;
private:
static int sz;
int index;
string name;
// constructor are private
GuitarBrand() {}
GuitarBrand(string str) : name(str) { index = sz++; }
// if we want to support iterating:
// vector<GuitarBrand*> vec;
// GuitarBrand(string str) : name(str) {
// vec.push_back(this);
// index = sz++;
// }
public:
string get_name(void) const { return this->name; }
int get_enum_size(void) const { return this->sz; }
operator int(void) const { return this->index; }
};
int GuitarBrand::sz = 0;
const GuitarBrand GuitarBrand::FENDER("Fender");
const GuitarBrand GuitarBrand::GIBSON("Gibson");
const GuitarBrand GuitarBrand::PRS("PRS");
// We also add initialization here for Ibanez:
const GuitarBrand GuitarBrand::IBANEZ("Ibanez");
As we can see, the GuitarBrand
class is self-sustaining.
All member functions and related fields are enclosed within the class.
If we want to add another GuitarBrand
, i.e. Ibanez, all we need to do is to create
another static const GuitarBrand
instance, and initialize it.
The static int sz
tracks the number of different GuitarBrand
instances (types), and
also assign a proper index
for each type of guitar brand.
If we like to provide the functionality of iteration of all guitar brands, we
could also add a vector<GuitarBrand*>
as private member and push_back(this)
during each guitar brand construction. The int
conversion function enables the use
of switch
statement on GuitarBrand
objects.
The following is a sample usage of the GuitarBrand
enum class.
class Guitar {
public:
const GuitarBrand* brand;
public:
void setBuilder( const GuitarBrand & b) { brand = &b ; }
string getBrand() const {
return brand->get_name();
}
};
int main( int argc, char * argv[] ){
Guitar g;
g.setBuilder(GuitarBrand::PRS);
cout << "PRS = " << GuitarBrand::PRS << '\n';
cout << g.getBrand() << '\n';
GuitarBrand gb ( GuitarBrand::PRS );
gb = GuitarBrand::GIBSON;
cout << GuitarBrand::get_enum_size() << '\n';
switch( gb ){
case 0:
cout << "FENDER\n";
break;
case 1:
cout << "GIBSON\n";
break;
case 2:
cout << "PRS\n";
break;
default:
cout << "UNKNOWN\n";
}
return 0;
}