How to automatically cast to children types in a vector<Parent*>?

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
33
34
35
36
37
38
39
40

class A {
	public:
		void log() {
			cout << 0 << endl;
		}
};

class B : public A {
	public:
		void log() {
			cout << 1 << endl;
		}
};

class C : public A {
	public:
		void log() {
			cout << 2 << endl;
		}
};

// ... a lot of subclasses of A

int main() {

	B b;
	C c;

	vector<A*> list = { &b, &c };

	for (int i = 0; i < list.size(); i++) {
		list[i]->log(); // all 0
	}

	b.log(); // 1
	c.log(); // 2

}


How to call log() function that's defined by every subclasses of A in vector<A*>?
Change class A to read:
1
2
3
4
5
6
7
8
9
class A {
	public:
		// virtual: allow this method to be overridden by subclasses
		virtual void log() {
			cout << 0 << endl;
		}

		virtual ~ A() = default;
};


Because dynamic dispatch incurs a run-time cost, C++ won't supply it unless you explicitly opt-in.
Last edited on
@mbozzi
Is there a point for using = default for the destructor?
@mbozzi

Thanks!

What about class C has a member function void log2() that class A doesn't have and I want to call it from a A* collection, how can I do it?

In my case I have something like:

1
2
3
4
5
6
7
8
9
class Comp {}

class Sprite : public Comp {
    void Sprite::set_origin(float, float);
}

e.comps = unordered_map<string, Comp*>;
e.comps["sprite"] = new Sprite();
static_cast<Sprite*>(e.comps["sprite"])->set_origin(0.5, 0.5);


the static_cast<Sprite*>(e.comps["sprite"]) is so verbose, and adding virtual void Comp::set_origin(float, float) doesn't make sense and there'll be lots of subclasses of Comp that have custom methods. I'm wondering if there's any shortcuts.
Last edited on
coder777 wrote:
Is there a point for using = default for the destructor?

We must mention the destructor because the implicit one would be non-virtual. If the base-class destructor was non-virtual, it would be undefined behavior to delete derived class objects through a pointer to base.
https://stackoverflow.com/questions/461203/when-to-use-virtual-destructors

As opposed to an empty definition (virtual ~ A() {}), the distinction is that the empty-braces makes a user-provided destructor, and in that case the implicit generation of defaulted copy constructors and copy assignment operators is deprecated. For example, experimental GCC 9 emits a warning if you put braces and try to copy: https://godbolt.org/z/C4aSno .

This works with the Rule of Three: if one has a class that needs a user-provided destructor, then that class probably needs a user-provided copy-constructor & copy assignment operator, or else shouldn't be copyable at all.

The consequence is simple: if a class is not managing resources and is therefore conforming to the Rule of Zero, then either don't mention the destructor at all (if possible) or explicitly default it on its first declaration otherwise - don't write empty braces.

Further, to the programmer, the intent is clearer.
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-eqdefault

This wording is in the current working draft:
http://eel.is/c++draft/depr.impldec#1

wengwengweng wrote:
What about class C has a member function void log2() that class A doesn't have and I want to call it from a A* collection, how can I do it?
Maybe the right cast there is dynamic_cast? (Unless you're certain that you'll never pick the wrong type.)

In any event:

If adding a base-class method doesn't make sense for all derived classes, then your class hierarchy violates the Liskov Substitution Principle. The best solution is probably to refactor the design. Maybe your objects should have sprites instead of be sprites?
https://en.wikipedia.org/wiki/Liskov_substitution_principle

If you are using a class hierarchy to approximate a discriminated union, use a discriminated union directly. The standard one is called std::variant, but you could also reach for the boost libraries (boost::variant) if you don't have the luxury of a new compiler.

The right answer depends on circumstances. What are you trying to do?
Last edited on
@mbozzi

Thanks for the reply and explanation!

I'm basically making a component system where there are many components, (Sprite, Box, Projectile, ... these classes inherited from Comp will never be inherited again). An entity has a list of components and is only responsible calling draw and updates of all components.

Now all of my components are inherited from a Comp class which contains some virtual functions that a component must have, and a unordered_map<string, Comp*> storing all the components in an Entity instance. I'm not sure if this is the best structure to do this.

I didn't know std::variant before I'll look it up now. (literally just started learning c++ yesterday sorry for too many questions. Really appreciate if you could give me some suggestions on my scenario.)
Last edited on
Sorry for the late response - I've been busy today preparing for the holiday and with other things.

Literally just started learning c++ yesterday
The topic question let me know you're new to C++, but not to programming :). The usual advice is that you take a bit of time to read A Tour of C++ (c. 180 pages) - the drafts linked on this page will be just fine:
https://isocpp.org/tour

I'm afraid I can't suggest much about your component system, because I don't know the specifics of your program. In particular, I don't know the use case nor how flexible the system needs to be, nor anything about required throughput.

I can share a sketch of a design I've used before:
http://www.cplusplus.com/forum/general/232926/
That presupposes we can't afford to write a subclass of Entity for every possible combination of behaviors. Your case may differ, but this is the problem a component system is usually supposed to solve.

Now all of my components are inherited from a Comp class which contains some virtual functions that a component must have, and a unordered_map<string, Comp*> storing all the components in an Entity instance. I'm not sure if this is the best structure to do this.
Consider switching to std::unordered_map<string, std::unique_ptr<Comp>>, but the choice of unordered_map is probably okay, barring performance concerns.

The current container of raw pointers will leak memory if an exception is thrown from the map, and places the burden for resource management on the programmer. The core guidelines has quite a lot to say about this topic:
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-resource

unique_ptr:
https://stackoverflow.com/questions/106508/what-is-a-smart-pointer-and-when-should-i-use-one
Last edited on
@mbozzi
I too want to say thanks for your clear explanation. The rule of zero is actually interesting.
I'm now using this which completely meets my needs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <typename T, typename... Args>
T* Entity::as(Args&&... args) {

	T* c(new T(std::forward<Args>(args)...));
	c->entity = this;
	c->init(this);
	this->comps[typeid(T)] = c;

	return c;

}

template<typename T>
T* Entity::get() {

	if (!this->is<T>()) {
		return nullptr;
	}

	return static_cast<T*>(this->comps[typeid(T)]);

}
Last edited on
Topic archived. No new replies allowed.