Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Applying "using" keyword on C++ pure virtual function

The Class B is overriding the pure Virtual Function "print()" of class A. Class C is inheriting Class B as well as having a "using A::print" statement. Now why Class C is not an abstract class?

class A {
    public :
        virtual void print() =0;
};

class B:public A {
    public:
        void print();
};

void B :: print() {

    cout << "\nClass B print ()";
}

class C : public B {

    public:
        using A::print;
};

void funca (A *a) {

    // a->print(1);                    
}

void funcb (B *b) {

    b->print();         
}

void funcc (C *c) {

    c->print();             
}

int main() {
    B b;
    C c;        
    funca(&c);              
    funcb(&c);              
    funcc(&c);              
    return 0;               
}

Output:

    Class B print ()
    Class B print ()
like image 479
Gtrex Avatar asked Jan 04 '19 12:01

Gtrex


People also ask

Which keyword is used to declare as a pure virtual function?

Which keyword is used to declare virtual functions? Explanation: The virtual keyword is used to declare virtual functions. Anonymous keyword is used with classes and have a different meaning. The virtual functions are used to call the intended function of the derived class.

Which keywords use virtual functions?

In C++, the virtual keyword is used to create a virtual function.

What is the use of pure virtual function in C?

Master C and Embedded C Programming- Learn as you go A pure virtual function is a virtual function in C++ for which we need not to write any function definition and only we have to declare it. It is declared by assigning 0 in the declaration.

What is virtual function when the virtual keyword is used describe?

A C++ virtual function is a member function in the base class that you redefine in a derived class. It is declared using the virtual keyword. It is used to tell the compiler to perform dynamic linkage or late binding on the function.


2 Answers

Based on my first attempt to find an answer, @Oliv's comments and answer, let me try to summarize all possible scenarios for a using A::memberFct declaration inside C.

  • A's member function is virtual and overridden by B
  • A's member function is non-virtual and hidden by B
  • A's member function is non-virtual and hidden by C itself

A small example for these cases is as follows.

struct A {
   virtual void f() {}
   void g() {}
   void h() {}
};

struct B : A {
   void f() override {}
   void g() {}
};

struct C : B {
   using A::f; // Virtual function, vtable decides which one is called
   using A::g; // A::g was hidden by B::g, but now brought to foreground
   using A::h; // A::h is still hidden by C's own implementation
   void h() {}
};

Invoking all three functions through C's interface leads to different function calls:

C{}.f(); // calls B::f through vtable
C{}.g(); // calls A::g because of using declarative
C{}.h(); // calls C::h, which has priority over A::h

Note that using declarations inside classes have limited influence, i.e., they change name lookup, but not the virtual dispatch (first case). Whether a member function is pure virtual or not doesn't change this behavior. When a base class function is hidden by a function down the inheritance hierarchy (second case), the lookup is tweaked such that the one subject to the using declaration has precedence. When a base class function is hidden by a function of the class itself (third case), the implementation of the class itself has precedence, see cppreference:

If the derived class already has a member with the same name, parameter list, and qualifications, the derived class member hides or overrides (doesn't conflict with) the member that is introduced from the base class.

In your original snippet, C is hence not an abstract class, as only the lookup mechanism for the member function in question is influenced by the using declarations, and the vtable points doesn't point to the pure virtual member function implementation.

like image 72
lubgr Avatar answered Nov 04 '22 15:11

lubgr


This is because using declaration does not introduce a new member or a new definition. Rather it introduces a set of declaration that can be found by qualified name look up [namespace.udecl]/1:

Each using-declarator in a using-declaration, introduces a set of declarations into the declarative region in which the using-declaration appears. The set of declarations introduced by the using-declarator is found by performing qualified name lookup ([basic.lookup.qual], [class.member.lookup]) for the name in the using-declarator, excluding functions that are hidden as described below.

It only has influence on the entity(ies) found by qualified name lookup. As such, it does not have influence in the definition of the final overrider [class.virtual]/2:

[...] A virtual member function C::vf of a class object S is a final overrider unless the most derived class ([intro.object]) of which S is a base class subobject (if any) declares or inherits another member function that overrides vf.

Which has a different meaning than: the final overrider is the entity designated by the expression D::vf where D is the most derived class of which S is a base class suboject.

And as a consequence, it does not influence if a class is an abstract class [class.abstract]/4:

A class is abstract if it contains or inherits at least one pure virtual function for which the final overrider is pure virtual.


Note 1:

The consequence is that a using directive will result in different behavior for non virtual and virtual functions [expr.call]/3:

If the selected function is non-virtual, or if the id-expression in the class member access expression is a qualified-id, that function is called. Otherwise, its final overrider in the dynamic type of the object expression is called; such a call is referred to as a virtual function call.

Simply:

  • non virtual function => function found by qualified name lookup
  • virtual function => call the final overrider

So if print was not virtual:

class A {
  public :
  void print() {
    std::cout << "\n Class A::print()";
    }
  };

int main() {
  B b;
  C c;        
  b.print() // Class B print ()
  c.print() // Class A print ()
  //Equivalent to:
  c.C::print() // Class A::print()             
  return 0;               
  }

Note 2:

As some may have noticed in the preceding standard paragraph, it is possible to performe a qualified call of a virtual function to get the non-virtual behavior. So a using declaration of virtual function may be practical (probably a bad practice):

class A {
  public :
  virtual void print() =0;
  };

//Warning arcane: A definition can be provided for pure virtual function
//which is only callable throw qualified name look up. Usualy an attempt
//to call a pure virtual function through qualified name look-up result
//in a link time error (that error message is welcome).
void A::print(){ 
  std::cout << "pure virtual A::print() called!!" << std::endl;
  }

int main() {
  B b;
  C c;        
  b.print() // Class B print ()
  c.print() // Class B print ()
  c.C::print() // pure virtual A::print() called!!
  //whitout the using declaration this last call would have print "Class B print()"              
  return 0;               
  }

Live demo

like image 22
Oliv Avatar answered Nov 04 '22 14:11

Oliv