Aggregate parameters

With aggregate parameters, we create a struct or a class that contains all the values, instead of adding one parameter per value. We don't have to be limited to one aggregate; for example, our city may take several structs, one for all terrain-related features that the game sets, and another for all features that the player controls directly:

struct city_features_t {
size_t number_of_buildings;
size_t number_of_towers;
size_t guard_strength;
enum center_t { KEEP, PALACE, CITADEL };
center_t center = KEEP;
bool with_forge = false;
bool with_granary = false;
bool with_temple;
};
struct terrain_features_t {
bool has_fresh_water;
bool is_coastal;
bool has_forest;
bool is_desert;
};
class City {
public:
City(city_features_t city_features, terrain_features_t terrain_features);
...
};

This solution has many advantages. First of all, assigning values to the arguments has to be done explicitly, by name, and is very visible:

city_features_t city_features;
city_features.number_of_buildings = 2;
city_features.center = city_features::KEEP;
...
terrain_features_t terrain_features;
terrain_features.has_fresh_water = true;
...
City Capital(city_features, terrain_features);

It is much easier to see what each argument's value is, and mistakes are much less likely. If we need to add a new feature, most of the time we just have to add a new data member to one of the aggregate types. Only the code that actually deals with the new argument has to be updated; all the functions and classes that simply pass the arguments and forward them do not need to change at all. We can even give the aggregate types default constructors to provide default values for all arguments:

struct terrain_features_t {
bool has_fresh_water;
bool is_coastal;
bool has_forest;
bool is_desert;
terrain_features_t() :
has_fresh_water(true),
is_coastal(false),
has_forest(true),
is_desert(false)
{}
};

This is, overall, an excellent solution to the problem of functions with many parameters. However, it has one drawback: the aggregates have to be explicitly created and initialized, line by line. This works out fine for many cases, especially when these classes and structs represent state variables that we are going to keep for a long time. But, when used purely as parameter containers, they create unnecessarily verbose code, starting from the fact that the aggregate variable must have a name. We don't really need that name, as we are going to use it only once to call the function, but we have to make one up. It would be tempting just
to use a temporary variable:

City Capital(city_features_t() ... arguments somehow go here? ... );

This would work if we could assign the values to the data members. We could do it with a constructor:

struct terrain_features_t {
bool has_fresh_water;
bool is_coastal;
bool has_forest;
bool is_desert;
terrain_features_t(
bool has_fresh_water,
bool is_coastal,
bool has_forest,
bool is_desert
) :
has_fresh_water(has_fresh_water),
is_coastal(is_coastal),
has_forest(has_forest),
is_desert(is_desert)
{}
};

City Capital(city_features_t(...),
terrain_features_t(true, false, false, true));

This works, but it brings us full circle, right to where we started; a function with a long list of easily mixed Boolean arguments. The fundamental problem we encounter is that C++ functions have positional arguments, and we are trying to come up with something that would let us specify arguments by name. Aggregate objects resolve this problem mostly as a side effect, and if the overall design benefits from collecting a group of values into one class, you should certainly do it. However, as a solution specifically for the problem of named arguments, with no other, more permanent reason to group the values together, they fall short. We will now see how this deficiency can be addressed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.111.22