For an overloaded function, calling specialized version for parent and child instances

前端 未结 5 930
独厮守ぢ
独厮守ぢ 2021-01-18 12:07

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 c
相关标签:
5条回答
  • 2021-01-18 12:30

    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.

    0 讨论(0)
  • 2021-01-18 12:39

    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

    0 讨论(0)
  • 2021-01-18 12:40

    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.

    0 讨论(0)
  • 2021-01-18 12:43

    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();
    
    0 讨论(0)
  • 2021-01-18 12:43

    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();
    }
    
    0 讨论(0)
提交回复
热议问题