Virtual inheritance and static inheritance - mixin

2019-02-03 12:43发布

问题:

If you have something like this:

#include <iostream>

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

class B : public A<B>
{
public:
    virtual void func()
    {
        std::cout << "into func";
    }
};

class C : public B
{
};

int main()
{
  C c;
  c.func();

  return 0;
}

Is func() dynamically dispatched?
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?

Edit: My code didn't compile? Sorry guys. I'm kinda ill right now. My new code also doesn't compile, but that's part of the question. Also, this question is for me, not the faq.

#include <iostream>

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

class B : public A<B>
{
public:
    virtual void func()
    {
        std::cout << "in B::func()\n";
    }
};

class C : public B
{
public:
    virtual void func() {
        std::cout << "in C::func()\n";
    }
};
class D : public A<D> {
    void func() {
        std::cout << "in D::func()\n";
    }
};
class E : public D {
    void func() {
        std::cout << "in E::func()\n";
    }
};

int main()
{
  C c;
  c.func();
  A<B>& ref = c;
  ref.func(); // Invokes dynamic lookup, as B declared itself virtual
  A<D>* ptr = new E;
  ptr->func(); // Calls D::func statically as D did not declare itself virtual
  std::cin.get();

  return 0;
}

visual studio 2010\projects\temp\temp\main.cpp(8): error C2352: 'B::func' : illegal call of non-static member function
      visual studio 2010\projects\temp\temp\main.cpp(15) : see declaration of 'B::func'
      visual studio 2010\projects\temp\temp\main.cpp(7) : while compiling class template member function 'void A<T>::func(void)'
      with
      [
          T=B
      ]
      visual studio 2010\projects\temp\temp\main.cpp(13) : see reference to class template instantiation 'A<T>' being compiled
      with
      [
          T=B
      ]

回答1:

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;
}


回答2:

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.



回答3:

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.



回答4:

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?



回答5:

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'.



回答6:

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.