Применение ключевого слова с использованием чистой виртуальной функции С++

Класс 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 ()

person Gtrex    schedule 04.01.2019    source источник
comment
Это связано с тем, что объявление using не является переопределением.   -  person StoryTeller - Unslander Monica    schedule 04.01.2019


Ответы (2)


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

  • Функция-член A является виртуальной и переопределяется B.
  • Функция-член A не является виртуальной и скрыта B
  • Функция-член A не является виртуальной и скрыта самим C

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

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

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

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

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

person lubgr    schedule 04.01.2019
comment
Это просто неправильно, derived class already has a member означает член, объявленный первым в этом классе. См. демонстрацию здесь: godbolt.org/z/ff5cEb - person Oliv; 04.01.2019
comment
@Oliv Не уверен, понимаю ли я вашу точку зрения, почему он ведет себя иначе, чем в зависимости от виртуальности рассматриваемой функции-члена? Если скрытие или переопределения обоих приводит к исключению набора объявлений, представленных объявлением using, не должны ли они вести себя одинаково? - person lubgr; 04.01.2019
comment
Ваше право это не распространяется слишком на этот случай. Ни один из стандартных абзацев, которые вы размещаете... - person Oliv; 04.01.2019
comment
Кажется, я нашел объяснение. Что ты думаешь об этом? - person Oliv; 04.01.2019

Это связано с тем, что использование объявления не вводит новый член или новое определение. Скорее, он вводит набор объявлений, которые можно найти с помощью поиска по полному имени [namespace.udecl]/1:

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

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

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

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

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

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


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

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

Если выбранная функция не является виртуальной или если id-выражение в выражении доступа к члену класса является квалифицированным 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;               
  }

Текущая демонстрация

person Oliv    schedule 04.01.2019
comment
Может быть, будет полезен комментарий о том, как using A::f влияет на поиск невиртуальной скрытой функции-члена f? В любом случае, отличное стандартное копание! - person lubgr; 04.01.2019
comment
@lubgr Я сделал это, пример, который я привожу, просто чист и, вероятно, сбивает с толку. - person Oliv; 04.01.2019
comment
Ах, извините за неясность, на самом деле я имел в виду поведение, которое вы указали ранее. Если A::print не-виртуальный, то using A::print в C действительно приводит к вызову A::print через C. - person lubgr; 04.01.2019
comment
@lubgr Ааааааа! - person Oliv; 04.01.2019
comment
См. [namespace.udecl]/2 Каждое использование-объявление является объявлением [...], которое прямо противоречит вашему утверждению. Таким образом, использование объявления не является объявлением. Я думаю, что вы пытаетесь сказать, что объявление использования, которое называет функцию, не является объявлением функции и т.д. - person M.M; 05.06.2019
comment
@М.М. Я полагаю, я хотел выразить, что объявление использования не вводит новый член. - person Oliv; 05.06.2019