Expert C++
上QQ阅读APP看书,第一时间看更新

Mimicking a class

A struct allows us to group variables, name them, and create objects. The idea of a class is to include the corresponding operations in the object, grouping both data and operations that are applicable to that particular data. For example, for the object of the Product type, it will be natural to call the set_rating() function on the object directly, rather than having a separate global function that takes a Product object via a pointer and modifies it. However, as we agreed to use structs in the C manner, we can't afford it to have member functions. To mimic a class using a C struct, we have to declare functions that work with the Product object as global functions, as shown in the following code:

struct Product {
std::string name;
double price;
int rating;
bool available;
};

void initialize(Product* p) {
p->price = 0.0;
p->rating = 0;
p->available = false;
}

void set_name(Product* p, const std::string& name) {
p->name = name;
}

std::string get_name(Product* p) {
return p->name;
}

void set_price(Product* p, double price) {
if (price < 0 || price > 9999.42) return;
p->price = price;
}

double get_price(Product* p) {
return p->price;
}

// code omitted for brevity

To use the struct as a class, we should manually call the functions in the proper order. For example, to use the object with properly initialized default values, we have to call the initialize() function first:

int main() {
Product cpp_book;
initialize(&cpp_book);
set_name(&cpp_book, "Mastering C++ Programming");
std::cout << "Book title is: " << get_name(&cpp_book);
// ...
}

This seems doable, but the preceding code will quickly turn into an unorganized mess if new types are added. For example, consider the Warehouse struct that keeps track of products:

struct Warehouse {
Product* products;
int capacity;
int size;
};

void initialize_warehouse(Warehouse* w) {
w->capacity = 1000;
w->size = 0;
w->products = new Product[w->capacity];
for (int ix = 0; ix < w->capacity; ++ix) {
initialize(&w->products[ix]); // initialize each Product object
}
}

void set_size(int size) { ... }
// code omitted for brevity

The first obvious issue is the naming of functions. We had to name the initializer function of the Warehouse initialize_warehouse to avoid conflict with the already declared initialize() function for the Product. We might consider renaming the functions for the Product type to avoid possible conflicts in the future. Next comes the mess with functions. Now, we have a bunch of global functions, which will increase in number as we add new types. It will be even more unmanageable if we add some hierarchy of types.

Though compilers tend to translate classes into structs with global functions, as we showed earlier, C++ and other high-level programming languages solve these and other issues that had not been mentioned by, introducing classes with smooth mechanisms of organizing them into hierarchies. Conceptually, keywords (class, public, or private) and mechanisms (inheritance and polymorphism) are there for developers to conveniently organize their code, but won't make the life of the compiler any easier.