可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I asked a question earlier but it turns out my problem was not properly modeled by my example. So here is my actual problem:
- I have class
A
, and class B
inheriting from A
,
- I have two functions
foo(A&)
and foo(B&)
,
- I have a list of
A*
pointers, containing instances of A
and B
.
- How do I get to call
foo(A&)
for instances of A
and foo(B&)
for instances of B
? Constraints: I can modify A
and B
implementation, but not foo
's implementation.
See below an example:
#include <iostream>
#include <list>
class A {
public:
};
class B : public A {
public:
};
void bar(A &a) { std::cout << "This is an A" << std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }
int main(int argc, char **argv) {
std::list<A *> l;
l.push_back(new B());
l.push_back(new B());
for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
bar(**it);
}
Although I am using a container with pointers, bar
is called with object from the parent class, not the child class:
# ./a.out
This is an A
This is an A
#
I was expecting
This is a B
Passing pointers to bar
(by rewriting its signature) does not help.
Thx to Antonio for helping clarifying the question.
回答1:
Since overloading is resolved at compile time, you need to supply the compiler with enough information to decide on the proper overload of bar
to call. Since you wish to make that decision dynamically based on the run-time type of the object, virtual functions would be of great help:
struct A {
virtual void bar() { bar(*this); }
};
struct B : public A {
virtual void bar() { bar(*this); }
};
It may seem like the bodies are identical, so B::bar
could be eliminated, but this is not true: although the bodies look exactly the same, they call different bar
s due to the static resolution of overloads in C++:
- Inside
A::bar
the type of *this
is A&
, so the first overload is called.
- Inside
B::bar
the type of *this
is B&
, so the second overload is called.
Modify the calling code to call the member bar
will complete the change:
std::list<A *> l;
l.push_back(new B());
l.push_back(new B());
for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
(*it)->bar();
回答2:
Edit: This answer the first version of the question, now see instead dasblinkenlight's solution.
If you do:
A* b = B();
Then *b
will be of type A. That's what you are doing in your for cycle. There's no "virtuality" or polimorfism involved in this.
The following code gives the behaviour you are looking for:
class A {
public:
virtual void bar() { std::cout << "This is an A" << std::endl; }
};
class B : public A {
public:
virtual void bar() { std::cout << "This is a B" << std::endl; }
};
int main(int argc, char **argv) {
std::list<A *> l;
l.push_back(new B());
l.push_back(new B());
l.push_back(new A());
l.push_back(new B());
for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
(*it)->bar();
}
Taking my example above, in that case:
b->bar();
will print This is a b
.
回答3:
You are looking for run-time polymorphism. This is supported "naturally" for virtual member methods.
An alternative would be to use RTTI and dynamically cast A*
to B*
and call bar
upon success... or static_cast
if you are really sure there are B*
objects. Generally need to down-cast indicates problematic design.
Important note: Run-time check in dynamic_cast requires the type to be polymorphic anyways. Maybe your particular A
fulfills this but you just can't change the class. If not, static_cast
is the only option available.
If you have control over class you, can use standard polymorphism and overload mechanisms using virtual methods on this
as a facade for the "external" call:
#include <iostream>
#include <list>
class A;
void external_bar(A&);
class A {
public:
virtual void bar() { external_bar(*this); };
};
class B;
void external_bar(B&); //IMPORTANT
class B : public A {
public:
virtual void bar() { external_bar(*this); };
};
void external_bar(A &a) { std::cout << "This is an A" << std::endl; }
void external_bar(B &b) { std::cout << "This is a B" << std::endl; }
int main(int argc, char **argv) {
std::list<A *> l;
l.push_back(new B());
l.push_back(new B());
for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
(*it)->bar();
}
This also has drawbacks. Forward declarations are needed. And you need to take care everything is defined properly, because if you forget line // IMPORTANT
the compiler will pick up the definition of external_bar
for A&
as it is implicitly convertible, and you might get quite a headache spotting the error.
回答4:
Others have already explained how it can be achieved.
I'll just limit myself to why it is so.
B gets implicitly cast to A here. So it currently has only properties of A.
The upward casting is implicit in C++.
Downcasting in C++ is possible only if your base class is polymorphic.
In short polymorphic requirement is nothing but something in your base class that can be overridden by your derived!! Virtual methods
then you can use RTTI and dynamic_cast as prescribed by others to do that.
Example:
#include <iostream>
#include <list>
class A {
public:
virtual void dummy() = 0;
};
class B : public A {
public:
void dummy() { }
};
void bar(A &a) { std::cout << "This is an A" << std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }
int main(int argc, char **argv) {
std::list<A *> l;
l.push_back(new B());
l.push_back(new B());
//Prints A
for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
bar(**it);
//Prints B
for (std::list<A *>::iterator it = l.begin(); it != l.end(); ++it)
bar(dynamic_cast<B&>(**it));
}
Answer:
This is an A
This is an A
This is a B
This is a B
Note: This is only if your list has objects of type B. Otherwise, its going to crash out. This only explains upcast vs downcast
回答5:
Antonio wrote a good solution involving virtual functions. If you really do not want to use a virtual function for some reason, then you can use dynamic_cast
in a free function instead:
#include <iostream>
#include <list>
struct A {
virtual ~A() {} // important
};
struct B : A {};
void bar(A &a) { std::cout << "This is an A" << std::endl; }
void bar(B &b) { std::cout << "This is a B" << std::endl; }
void bar_helper(A *ptr)
{
if ( auto b = dynamic_cast<B *>(ptr) )
bar(*b);
else
bar(*ptr);
}
int main()
{
std::list<A *> ls;
ls.push_back(new B);
ls.push_back(new B);
ls.push_back(new A);
for (auto ptr : ls)
{
bar_helper(ptr);
delete ptr;
}
ls.clear();
}