2015-07-10 3 views
1

Я смотрел видео, которое можно найти на https://www.youtube.com/watch?v=4F72VULWFvc, и мне очень понравилась часть концепции для представленных случаев. Но я работаю со связанным списком и необходимости селективного выполнения метода, например:Наследование, выполнение выборочного метода

#include <stdio.h> 
class A { 
public: 
    A() : next(0) { 
     if (head == 0) { 
      head = this; 
     } else { 
      A* step = head; 
      while (step->next != 0) { 
       step = step->next; 
      } 
      step->next = this; 
     } 
    } 
    virtual ~A() { 
     if (head == this) { 
      head = 0; 
     } else { 
      A* step = head; 
      while (step->next != this) { 
       step = step->next; 
      } 
      step->next = next; 
     } 
    } 
    virtual void foo() { 
     // Do nothing... 
    } 
    static A* head; 
    A* next; 
}; 

class B : public A { 
public: 
    B() {} 
    virtual ~B() {} 
    virtual void foo() { 
     printf("function foo\n"); 
    } 
}; 

A* A::head = 0; 

int main() { 
    A a_cls; 
    B b_cls; 

    A* step = A::head; 

    while (step != 0) { 
     step->foo(); 
     step = step->next; 
    } 

    return 0; 
} 

После инстанцирования всех объектов, методу foo() объектов класса B нужен выполнить. Для этого виртуальный метод foo() добавлен в класс A с пустым телом virtual void foo() {}, а в классе B код добавляется к методу foo().

Это работает, но мне не нравится, в основной функции похоже, что вы делаете что-то на каждом узле, но вы этого не знаете, это почти похоже на указатель NULL. Есть ли еще одно творческое решение для этого?

Примечание: Я использую C++ 03.

ответ

1

Заканчивать dynamic_cast как способ проверки для конкретного производного типа и только называют foo на объекты класса B (или класса, производного от B):

int main() { 
    A a_cls; 
    B b_cls; 

    A* step = A::head; 
    B* step_b = 0; 

    while (step != 0) { 
     step_b = dynamic_cast<B *>(step); 
     if (step_b != 0) { 
      step_b->foo(); 
     } 
     step = step->next; 
    } 

    return 0; 
} 

Таким образом, нет необходимости в определении пустой метод foo на A. Попробуйте это на ideone.

+0

К ОП: Пожалуйста, поймите, что это хрупкий код. Как только вы вводите тип 'C' в качестве подтипа' A', вам понадобится другое предложение 'if'. Именно в этом и заключается причина использования полиморфизма. –

+0

Думаю, мне нужно будет увидеть больше намерения OP знать, согласен ли я с этим утверждением. Если C происходит от B, нет необходимости в дополнительной инструкции if. Если мы хотим вызывать другой метод для C, чем для B, нам нужен оператор if независимо. –

0

Это работает, но я не люблю его, в функции main это выглядит, как вы делаете что-то в каждом узле, но нет, он чувствует себя почти как NULL указатель. Есть ли еще одно творческое решение для этого?

Убедитесь, что ваш базовый класс является чистым абстрактным классом. Таким образом, каждый вызов step->foo();, скорее всего, что-то сделает.

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

#include <iostream> 
#include <string> 

class List 
{ 
    public: 

     class Node 
     { 
     public: 
      Node() : next(nullptr) {} 
      virtual ~Node() {} 
      virtual void print() = 0; 
      Node* next; 
     }; 

     List() : head(nullptr), tail(nullptr) {} 

     ~List() 
     { 
     Node* node = head; 
     Node* next = nullptr; 
     for (; node != nullptr; node = next) 
     { 
      next = node->next; 
      delete node; 
     } 
     } 

     void addNode(Node* node) 
     { 
     if (head == 0) 
     { 
      head = tail = node; 
      return; 
     } 

     if (head == tail) 
     { 
      head->next = node; 
     } 

     tail->next = node; 
     tail = node; 
     } 

     void print() 
     { 
     Node* node = head; 
     for (; node != nullptr; node = node->next) 
     { 
      node->print(); 
      std::cout << std::endl; 
     } 
     } 

     Node* head; 
     Node* tail; 
}; 

class NodeA : public List::Node 
{ 
    public: 
     NodeA(int d) : data(d) {} 
     virtual ~NodeA() {} 
     virtual void print() 
     { 
     std::cout << "In NodeA::print(), Data: " << data; 
     } 
    private: 
     int data; 
}; 


class NodeB : public List::Node 
{ 
    public: 
     NodeB(double d) : data(d) {} 
     virtual ~NodeB() {} 
     virtual void print() 
     { 
     std::cout << "In NodeB::print(), Data: " << data; 
     } 
    private: 
     double data; 
}; 


class NodeC : public List::Node 
{ 
    public: 
     NodeC(std::string const& d) : data(d) {} 
     virtual ~NodeC() {} 
     virtual void print() 
     { 
     std::cout << "In NodeC::print(), Data: " << data; 
     } 
    private: 
     std::string data; 
}; 

int main() 
{ 
    List list; 

    list.addNode(new NodeA(10)); 
    list.addNode(new NodeB(23.45)); 
    list.addNode(new NodeC("abcd")); 

    list.print(); 

    return 0; 
} 
+0

А? как это может помочь? Вы все равно должны определять каждую виртуальную функцию в каждом классе. Если я чего-то не упущу. – user1135541

+0

@ пользователь1135541, не соответствует действительности. Вам нужно будет определить их для каждого конкретного класса. Если вы разделите свои классы на «Список» и «ListNode» и сделайте «ListNode» чистым виртуальным классом, это будет иметь больше смысла. –

+0

Мне нравится эта идея, но я думаю, что ответ должен быть немного более подробным, чтобы ответить на комментарий OP. Это связано с намерением: какова истинная цель класса А? Это просто построить список других объектов? Если это так, то, вероятно, должно быть абстрактным. –

0

Это работает, но мне не нравится это, в основном функции, похоже, вы делаете что-то в каждом узле, но вы не

На самом деле вы делаете что-то на каждом узле. То, что вы делаете, - это решение ничего не делать. Решения, как правило, не являются бесплатными, и вы недостаточно сообщили нам о заявке, чтобы оправдать предположение, что она может быть реструктурирована таким образом, чтобы решение могло быть эффективно бесплатным (сделано во время компиляции).

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

+0

Пользователь класса должен знать содержимое класса, чтобы знать, что происходит, поэтому логика распространяется, причем функция является просто функцией 'filler'. Так что это читаемость, которая мне не нравится. – user1135541

0

Хотя решение Остина совершенно прекрасное, другой подход (который я обычно предпочитаю) в таких ситуациях заключается в использовании интерфейсных классов и повышении уровня.

class FooFighter { public: virtual void foo() = 0; }; 

class A { ... }; 

class B1 : public A { ... }; 

class B2 : public A, public FooFighter { ... }; 

... 

int main() { 
    std::vector<A *> v; 

    // fill up v 

    for (int i = 0; i < v.size(); ++i) { 
    FooFighter * ff = dynamic_cast<FooFighter *>(v[i]); 
    if (ff) ff.foo(); 
    } 
    return 0; 

}

Это позволяет хранить, имеет ли класс Foo независимо от остальной части вашей иерархии. В частности, изучая иерархию классов любого типа, вы всегда знаете, реализует ли этот тип foo, потому что он должен наследовать FooFighter. Когда вы используете подход downcasting, вы можете иметь несколько дочерних элементов A, которые нетривиально реализуют foo разными способами. Этот подход также отлично сочетается с IDE, которые позволяют легко анализировать и перемещать иерархии типов в вашем коде.

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