For an overloaded function, calling specialized ve

2019-02-22 08:51发布

问题:

I asked a question earlier but it turns out my problem was not properly modeled by my example. So here is my actual problem:

  1. I have class A, and class B inheriting from A,
  2. I have two functions foo(A&) and foo(B&),
  3. I have a list of A* pointers, containing instances of A and B.
  4. 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 bars 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();
}