2012-07-08 3 views
2

Я изучаю C++ для экзамена, и я подумал, что я понял большую часть недоразумений в области C++ с большой усталостью, но я столкнулся с упражнением на прошедшем экзамене, который сводит меня с ума, это сочетает в себе виртуальные методы и наследование таким образом, что я не кажется, чтобы понять, вот код:C++ Наследование классов с функциями

#include <iostream> 

    class B; 

class A { 
    public: 
    virtual A* set(A* a) = 0; 
}; 

class B : public A { 
    public: 
    virtual A* set(B* b) { 
      std::cout << "set1 has been called" << std::endl; 
      b = this; 
      return b; 
    } 

    virtual B* set(A* a) { 
      std::cout << "set2 has been called" << std::endl; 
      a = this; 
      return this; 
    } 
}; 

int main(int argc, char *argv[]) { 
    B *b = new B(); 
    A *a = b->set(b); 
    a = b->set(a); 
    a = a->set(b); 
    a = a->set(a); 
    return 0; 
} 

выход

set1 has been called 
set2 has been called 
set2 has been called 
set2 has been called 

Из того, что я собрал первый вызов (b-> набор (b)) вызывает первый метод класса B и возвращает b, а затем этот объектre f получает casted до значения A, что теперь объект b теперь имеет тип A? поэтому у меня есть A * a = A * b; теперь имеет смысл для меня, что я должен назвать набор A, так как у меня есть такая ситуация в моем сознании objectoftypeA->set(objectoftypeA), так что я не должен смотреть в виртуальные методы, поскольку два объекта являются базовыми классами?

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

+1

Было бы полезно, если бы вы описали, что вы ожидаете *. Вы просто набросились на нас большим количеством кода и описали в предвыборном предложении, что вы не уверены, что немного сложно понять. Трудно знать, с чего начать отвечать. – Potatoswatter

+0

Я бы ожидал, что второй вызов снова вызовет set1, но это не так, я думаю, что вся моя линия мышления ошибочна – kdma

+1

Im не совсем уверен в этом, но пример действительно запутан, хотя. IMO, я думаю, что 'virtual B * set (A * a)' alias 'set2' будет действовать как реализация' virtual A * set (A * a) = 0; 'как' B * 'может быть неявно передана в' A * '. Таким образом, как только вы получили 'A *' из 'set1', будет вызван implmentatino, который является' set2'. EDIT: Обратите внимание, что 'set1' canant реализует чистую виртуальную функцию из-за своего специализированного параметра. Попробуйте прокомментировать каждую функцию. I gueess 'B * b = new B();' будет вызывать ошибку, если 'set2' не существует, но нет, если' set1' не существует. – Paranaix

ответ

2

Potatoswatter является правильным, но я думаю, что у меня есть немного более понятное объяснение. Я думаю, что OP путается с тем, что происходит во время выполнения с динамическим поиском по типу по сравнению с временем компиляции, и когда кастинг происходит автоматически, а если нет.

Во-первых, тип возврата НЕ влияет на вызванную перегрузку. Вы, наверное, знаете это, но это повторяется. Неправильное совпадение типа возврата вызывает ошибку во время компиляции, но не время выполнения, и не влияет на вызванную перегрузку. Также стоит отметить, что до тех пор, пока это совместимый тип указателя (в иерархии вместе), возвращающий указатель, он никогда не «меняет» его. Это все тот же указатель, в отличие от преобразования float в ints, где происходит фактическое изменение.

Теперь, чтобы пройти звонки один за другим. Это мое понимание процесса, не обязательно то, что стандарт, или что «действительно» происходит.

При вызове b->set(b) компилятор (не во время выполнения) идет «ищет метод с именем установить с аргументом указатель B», который он находит с той, которая выводит set1. Это виртуально, поэтому есть код, чтобы проверить, указывает ли класс на что-то меньшее, но его нет, поэтому он просто вызывает его и возвращает указатель this в a.

Теперь вы звоните b->set(a). Опять же, это компилятор, который идет «делает b есть перегрузка, которая принимает указатель на A?" Да, это так, поэтому он вызывает метод «set2». Это компилятор, который видит A*, и поэтому вызов «определяется» в этой точке. Хотя указатель указывает на объект, который имеет тип B, компилятор этого не знает или заботится. Итак, это типы времени компиляции аргументов , которые определяют, какой перегруженный метод получить. С того момента, когда в иерархии виртуальный объект берется, он находится на базовом типе указателя this, но только вниз.

Это другой случай. Попробуйте это: b->set(dynamic_cast<B*>(a)) Это должно вызывать метод set1, потому что у компилятора определенно есть указатель на B (даже если это nullptr).

Теперь третий случай: a->set(b). Что здесь происходит, компилятор говорит: «Есть только один метод set, так может ли аргумент быть настроен или построен для этого типа?» Ответ да, так как B является ребенком A. Таким образом, трансляция выполняется прозрачно, а компилятор вызывает диспетчер ABSTRACT для заданного метода типа A. Это происходит во время компиляции до «реальный» тип, на который указывает указатель a. Затем во время выполнения программа «ходит по виртуальному» и находит самый низкий, метод B->set(A*), который испускает «set2». Фактический тип того, что указывает аргумент, не используется, а только тип слева от оператора стрелки и который определяет только то, как далеко вниз по иерархии.

И четвертый случай - только третий. Тип аргумента (указатель, а не указатель на него) совместим, поэтому он по-прежнему. Если вы хотите драматической демонстрации этого, попробуйте следующее:

a->set((A*)nullptr) // prints "set2 has been called" 
b->set((A*)nullptr) // prints "set2 has been called" 
b->set((B*)nullptr) // prints "set1 has been called" 

Основной тип того, что аргументы указывают не влияет на динамическую отправку. Только их «поверхностный» тип влияет на вызываемую перегрузку.

+0

Я, наконец, понял, что ваше объяснение было более простым и более целенаправленным для моего незнания C++. Я бы хотел, чтобы мой учитель мог так объяснить, некоторые люди забывают, что все начинают без знания и нуждаются в пошаговом руководстве для некоторых вещей – kdma

4

Программа демонстрирует, как функции членов просматриваются. Статический тип объекта определяет функцию перегрузки, которая будет вызываться: она выполняет поиск имени. Затем динамический тип определяет виртуальное переопределение, которое вызывается.

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

С A имеет только один член set, есть только одна вещь, которая может произойти, когда вы звоните a->set(), независимо от того, что аргумент. Но когда вы вызываете b->set(), есть пара потенциальных функций, и выбирается лучший.

С B::set никогда не отменяется, не имеет значения, является ли это virtual или нет. virtual Члены одного класса не разговаривают друг с другом вообще.

+0

+1, потому что это ответ OK, и мне довелось прочитать его. –

+0

@ Cheersandhth.-Alf Да, это так ... 90% – Potatoswatter

Смежные вопросы