Virtual inheritance and static inheritance - mixing in C++

前端 未结 6 647
误落风尘
误落风尘 2020-12-29 13:33

If you have something like this:

#include 

template class A
{
public:
    void func()
    {
        T::func();
    }
};

c         


        
相关标签:
6条回答
  • 2020-12-29 14:15

    How could you implement class A such that if B has a virtual override, that it is dynamically dispatched, but statically dispatched if B doesn't?

    As others have noticed, it's really hard to make sense of that question, but it made me remember something I have learned a long time ago, so here's a very long shot at answering your question:

    template<typename Base> class A : private Base
    {
    public:
        void func()
        {
            std::count << "A::func";
        }
    };
    

    Given this, it depends on A's base whether func() is virtual. If Base declares it virtual then it will be virtual in A, too. Otherwise it won't. See this:

    class V
    {
    public:
        virtual void func() {}
    };
    class NV
    {
    };
    
    class B : public A<V>  // makes func() virtual
    {
    public:
        void func()
        {
            std::count << "B::func";
        }
    };
    
    class C : public A<NV>  // makes func() non-virtual
    {
    public:
        void func()
        {
            std::count << "C::func";
        }
    };
    

    Would this happen to answer your question?

    0 讨论(0)
  • 2020-12-29 14:17

    In your particular example, there's no need for dynamic dispatch because the type of c is known at compile time. The call to B::func will be hard coded.

    If you were calling func through a B*, then you would be calling a virtual function. But in your highly contrived example, that would get you to B::func once again.

    It doesn't make much sense to talk about dynamic dispatch from an A* since A is a template class - you can't make a generic A, only one that is bound to a particular subclass.

    0 讨论(0)
  • 2020-12-29 14:21

    I'm not sure I understand what you're asking, but it appears you are missing the essential CRTP cast:

    template<class T>
    struct A {
      void func() {
        T& self = *static_cast<T*>(this);  // CRTP cast
        self.func();
      }
    };
    
    struct V : A<V> {  // B for the case of virtual func
      virtual void func() {
        std::cout << "V::func\n";
      }
    };
    
    struct NV : A<NV> {  // B for the case of non-virtual func
      void func() {
        std::cout << "NV::func\n";
      }
    };
    

    If T does not declare its own func, this will be infinite recursion as self.func will find A<T>::func. This is true even if a derived class of T (e.g. DV below) declares its own func but T does not.

    Test with different final overrider to show dispatch works as advertised:

    struct DV : V {
      virtual void func() {
        std::cout << "DV::func\n";
      }
    };
    struct DNV : NV {
      void func() {
        std::cout << "DNV::func\n";
      }
    };
    
    template<class B>
    void call(A<B>& a) {
      a.func();  // always calls A<T>::func
    }
    
    int main() {
      DV dv;
      call(dv);   // uses virtual dispatch, finds DV::func
      DNV dnv;
      call(dnv);  // no virtual dispatch, finds NV::func
    
      return 0;
    }
    
    0 讨论(0)
  • 2020-12-29 14:22

    How could you implement class A such that if B has a virtual override, that it is dynamically dispatched, but statically dispatched if B doesn't?

    Somewhat contradictory, isn't it? A user of class A may know nothing about B or C. If you have a reference to an A, the only way to know if func() needs dynamic dispatch is to consult the vtable. Since A::func() is not virtual there is no entry for it and thus nowhere to put the information. Once you make it virtual you're consulting the vtable and it's dynamic dispatch.

    The only way to get direct function calls (or inlines) would be with non-virtual functions and no indirection through base class pointers.

    Edit: I think the idiom for this in Scala would be class C: public B, public A<C> (repeating the trait with the child class) but this does not work in C++ because it makes the members of A<T> ambiguous in C.

    0 讨论(0)
  • 2020-12-29 14:22

    Whether the function is dynamically dispatched or not depends on two things:

    a) whether the object expression is a reference or pointer type

    b) whether the function (to which overload resolution resolves to) is virtual or not.

    Coming to your code now:

      C c; 
      c.func();   // object expression is not of pointer/reference type. 
                  // So static binding
    
      A <B> & ref = c; 
      ref.func(); // object expression is of reference type, but func is 
                  // not virtual. So static binding
    
    
      A<D>* ptr = new D; 
      ptr->func(); // object expression is of pointer type, but func is not 
                   // virtual. So static binding 
    

    So in short, 'func' is not dynamically dispatched.

    Note that :: suppresses virtual function call mechanism.

    $10.3/12- "Explicit qualification with the scope operator (5.1) suppresses the virtual "call mechanism.

    The code in OP2 gives error because the syntax X::Y can be used to invoke 'Y' in the scope of 'X' only if 'Y' is a static member in the scope of 'X'.

    0 讨论(0)
  • 2020-12-29 14:26

    Seems you just had to add a little trace and usage to answer your own question...

    #include <iostream>
    
    template<typename T> struct A { 
        void func() { 
            T::func(); 
        } 
    }; 
    
    struct B1 : A<B1> { 
        virtual void func() { 
            std::cout << "virtual void B1::func();\n";
        } 
    }; 
    
    struct B2 : A<B2> { 
        void func() { 
            std::cout << "void B2::func();\n";
        } 
    }; 
    
    struct C1 : B1 { }; 
    struct C2 : B2 { }; 
    
    struct C1a : B1 {
        virtual void func() {
            std::cout << "virtual void C1a::func();\n";
        }
    };
    
    struct C2a : B2 {
        virtual void func() {
            std::cout << "virtual void C2a::func();\n";
        }
    };
    
    int main()
    {
        C1 c1; 
        c1.func(); 
    
        C2 c2; 
        c2.func(); 
    
        B1* p_B1 = new C1a;
        p_B1->func();
    
        B2* p_B2 = new C2a;
        p_B2->func();
    }
    

    Output:

    virtual void B1::func();
    void B2::func();
    virtual void C1a::func();
    void B2::func();
    

    Conclusion: A does take on the virtual-ness of B's func.

    0 讨论(0)
提交回复
热议问题