0

So say I have the class empire. empire inherits populationContainer and landContainer as such:

class empire : public populationContainer, public landContainer

Both of those parent classes have the method update(), each of which do their own thing; for example, the pop container calculates pop growth and the land container calculates land expansion.

I currently cannot call empire.update() because it is ambiguous. How do I have both methods get called at once? Additionally, what if I also want empire to have more instructions under update()?

I can call each method in turn like such:

void empire::update() {
    populationContainer::update();
    landContainer::update();
    // Do other stuff
}

But doesn't this violate the open-closed principal? What if I want to add more 'container' classes with their own update() functions? I'd have to add each one to empire's update() function.

  • 2
    But should `empire` be a `populationContainer` and a `landContainer` or should it just contain `population` and `land`? – user4581301 Feb 04 '22 at 23:37
  • 1
    @user4581301 To be honest if I knew the answer to that question I'd be a much better programmer. Both of those classes have a lot of their own stuff, including maps and methods to mutate them. – Justin Iaconis Feb 04 '22 at 23:41
  • I assume you're referring to the fact that, if `empire` does not override the `update()` functions and `e` is an instance of `empire`, then a call of `e.update()` is ambiguous. That's the way it is - the standard does not require a function inherited from multiple distinct bases to be implicitly folded together into one in the derived class. The way to make it non-ambiguous is what you have done. It's not a violation of the open-closed principle, since you aren't modifying either of the base classes. Better if both inherited functions are `virtual`. – Peter Feb 04 '22 at 23:49

3 Answers3

1

My solution would be:

// If you add more base classes here, add them also to the 'update' method
class empire : public populationContainer, public landContainer
Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
1

OO, and in particular the default object model of C++, does not solve all problems.

If you have an object that is product of containers of other kinds of objects, with a set of operations you want to be able to dispatch to each of its parts, the built in C++ object model does not help you much.

There are a few approaches.

First, you can wait for compile time reflection. That is years off.

Second, you can use code generation. Qt uses this with its MOCs.

Third, you can require a central point where the empire class defines what it is a product over:

static auto asProduct(auto&& self){
  return std::forward_as_tuple( get<Base1>(decltype(self)(self), get<Base2>(decltype(self)(self), etc );
}

Third, you can use CRTP to generate the above asProduct from Derived and a list of bases.

Forth, you can generate your Empire class (or a base meta container) with a list of methods to wire up and containers. This is basically writing your own object model.

Fifth, you can just manually forward stuff in update.


In a related issue, is-a seems overused here. Your empire has lands and population. Using is-a here seems awkward. And an abstract object type for a container is also overkill; why not be concrete?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I don't think I understand what you mean when you say "is-a is overused". `empire` is an object that contains a population, so "`empire` is a `populationContainer`" makes sense to me. Similarly, `empire` is an object that stores land and `continent` is another object that stores land, so they are both `landContainer`s. – Justin Iaconis Feb 05 '22 at 02:02
  • Is-a also means that everywhere you use a `populationContainer` you must also be able to use an `empire`, and a lot of the time this does not make sense logically. Like perhaps you assign a `populationContainer` to a `village` to move population around. This means you must also be able to assign an `empire` to the `village`. But where I think Yakk is headed is Don't abstract if you aren't making use of the abstraction. Do you have enough different types like `village`, `hamlet`, and `county` you can all treat as `populationContainer`s to make having `populationContainer` make sense? – user4581301 Feb 05 '22 at 02:19
  • @justin Do all mutating operations on a population container have the same guarantees on everything cobtaining population? (The LSP) Does empire need to intercept and rewrite messages to its population container? Or would observation be sufficient? Or is observation even needed? I have a container of population is different than I am a subtype of containers of population. Product types (has a) are often better models than intersection types (is a). – Yakk - Adam Nevraumont Feb 05 '22 at 02:53
0

You need to hold somethere the list of Update functions, for example it may be located in virtual base class IUpdatable that has protected RegisterUpdateFunc(lambda) method and public Update() method that calls to all registered lambdas, one by one.

Now everybody who inherits this class and want to participiate in "update event" just calls once in his constructor

RegisterUpdateFunc([=](){ Update(); })
jenkas
  • 872
  • 14
  • 16