Применение ключевого слова "using" в чистой виртуальной функции C ++ - PullRequest
0 голосов
/ 04 января 2019

Класс B переопределяет чисто виртуальную функцию "print ()" класса A. Класс C наследует класс B, а также имеет оператор "using A :: print".Теперь, почему класс C не является абстрактным классом?

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

Выход:

    Class B print ()
    Class B print ()

Ответы [ 2 ]

0 голосов
/ 04 января 2019

Это потому, что при использовании объявления не вводится новый член или новое определение. Вместо этого представляет набор объявлений , которые можно найти по квалифицированному имени. Look up [namespace.udecl] / 1 :

Каждый декларатор использования в объявлении использования, вводит набор объявлений в декларативную область, в которой появляется объявление использования . Набор объявлений, введенных декларатором using, определяется путем выполнения поиска подходящего имени ([basic.lookup.qual], [class.member.lookup]) для имени в деклараторе использования, за исключением функций, которые скрыты, как описано ниже.

Он влияет только на сущность (и), найденную при поиске квалифицированного имени. Таким образом, он не влияет на определение окончательного переопределения [class.virtual] / 2 :

[...] Виртуальная функция-член C :: vf объекта класса S является окончательным переопределением, если только самый производный класс ([intro.object]), в котором S не является подобъектом базового класса (если есть) объявляет или наследует другую функцию-член , которая переопределяет vf.

Имеет значение, отличное от: конечный переопределитель - это сущность, обозначенная выражением D :: vf, где D - наиболее производный класс, из которого S является подобъектом базового класса.

И, как следствие, не влияет, является ли класс абстрактным классом [class.abstract] / 4 :

Класс является абстрактным, если он содержит или наследует хотя бы одну чисто виртуальную функцию, для которой конечный переопределитель является чисто виртуальным.


Примечание 1:

Следствием этого является то, что директива using приведет к разному поведению не виртуальных и виртуальных функций [expr.call] / 3:

Если выбранная функция не виртуальная или если id-выражение в выражении доступа к члену класса является qualid-id, вызывается эта функция. В противном случае вызывается его окончательное переопределение в динамическом типе выражения объекта; такой вызов называется вызовом виртуальной функции.

Просто:

  • не виртуальная функция => функция найдена с помощью поиска по квалифицированному имени
  • виртуальная функция => вызов окончательного переопределителя

То есть, если print не было виртуальным:

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

Примечание 2:

Как некоторые, возможно, заметили в предыдущем стандартном параграфе, можно выполнить квалифицированный вызов виртуальной функции, чтобы получить не виртуальное поведение. Поэтому объявление использования виртуальной функции может быть практичным (вероятно, плохой практикой):

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

Демонстрационная версия

0 голосов
/ 04 января 2019

Основываясь на моей первой попытке найти ответ, комментариях и ответе @ Oliv, позвольте мне попытаться обобщить все возможные сценарии для объявления using A::memberFct внутри C.

  • A является виртуальной и переопределяется на B
  • A не виртуальна и скрыта B
  • A не виртуальна и скрыта самой C 1016 *

Небольшой пример для этих случаев следующий:

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() {}
};

Вызов всех трех функций через интерфейс C приводит к вызовам различных функций:

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

Обратите внимание, что использование объявлений внутри классов имеет ограниченное влияние, то есть они изменяют поиск имени, но не виртуальную диспетчеризацию (первый случай). Является ли функция-член чисто виртуальной или нет, это не меняет этого поведения. Когда функция базового класса скрыта функцией вниз по иерархии наследования (второй случай), поиск настраивается таким образом, что тот, который подвергается объявлению using, имеет приоритет. Когда функция базового класса скрыта функцией самого класса (третий случай), реализация самого класса имеет приоритет, см. cppreference

Если производный класс уже имеет члена с таким же именем, списком параметров и квалификациями, член производного класса скрывает или переопределяет (не конфликтует) член, представленный в базовом классе.

В вашем исходном фрагменте, C, следовательно, не является абстрактным классом, поскольку декларации using влияют только на механизм поиска для рассматриваемой функции-члена, а точки vtable не указывают на чисто виртуальную функцию-член осуществление.

...