Полиморфизм и наследование статических членов в C ++ - PullRequest
1 голос
/ 21 декабря 2009

Мне нужно сохранить список (вектор) детей для каждого класса, и у меня есть хитрая проблема:

class A
{
protected:
    int a;
    static vector<A*> children; 
public:
    A(int a): a(a) {;};
    virtual void  AddToChildren(A* obj);
    virtual void  ShowChildren();
    virtual void Show();
};

class B: public A
{
protected:
    int b;
    static vector<A*> children;
public:
    B(int a, int b): b(b), A(a) { A::AddToChildren(this);};
    virtual void Show();
};

class C: public B
{
protected:
    int c;

public:
    C(int a, int b, int c): c(c), B(a,b) { B::AddToChildren(this);};
    virtual void Show();
};

vector<A*> A::children=vector<A*>();
vector<A*> B::children=vector<A*>();


void A::AddToChildren(A *obj)
{
    children.push_back(obj);
}

void A::ShowChildren()
{
    for(vector<A*>::iterator i=children.begin(); i!=children.end();i++)
        (*i)->Show();
}

Добавление A (0), B (1,1) и C (2,2,2) и вызов a.ShowChildren дает: 1,1; 2,2,2; 2,2,2

Каждый раз, когда я создаю экземпляр класса C, вместо B :: children и A :: children обновляется A :: children. Оооочень ... класс C добавляется дважды к потомкам класса A, но не добавляется к классу B. Это помогает, когда я копирую класс AddChildren ( буквально copy) в класс B, чтобы каждый класс имеет свой собственный AddChildren / ShowChildren. Также мне удалось выполнить эту задачу с помощью указателей, но мне интересно, есть ли лучший способ. Я думаю, что проблема где-то в «использовании правильного вектора», но я не знаю, как заставить компилятор использовать правильный.

Буду признателен за любые предложения по поводу того, что я здесь делаю неправильно.

Прежде всего, спасибо всем за ваши комментарии и помощь. Используя ваш совет (о моем дизайне и виртуальном GetList ()) мне удалось упростить мою программу:

class A
{
protected:
    int a;
    virtual vector<A*>* GetList();
public:
    A(int a): a(a) {;};
    A(int a, A* inherited):a(a) { AddToChildren(inherited);};

    static vector<A*> children; 

    virtual void AddToChildren(A* obj);
    virtual void ShowChildren();

    virtual void Show();
};

class B: public A
{
protected:
    int b;
    virtual vector<A*>* GetList();
public:
     static vector<A*> children;
     B(int a, int b): b(b), A(a,this){;};
     B(int a, int b, A* inherited) : b(b), A(a,this){AddToChildren(inherited);};

    virtual void Show();

};

class C: public B
{
protected:
    int c;
public:
    C(int a, int b, int c): c(c), B(a,b,this) { };
    virtual void Show();
    virtual vector<A*>* GetList();
};


vector<A*> A::children=vector<A*>();
vector<A*> B::children=vector<A*>();


void A::AddToChildren(A *obj)
{
    GetList()->push_back(obj);
}

void A::ShowChildren()
{
    for(vector<A*>::iterator i=GetList()->begin(); i!=GetList()->end();i++)
        (*i)->Show();
}


vector<A*> * A::GetList()
{

    return & children;
}

vector<A*> * B::GetList()
{

    return & children;
}

vector<A*> * C::GetList()
{

    return & children;
}

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

Еще раз спасибо всем за помощь.

Ответы [ 5 ]

4 голосов
/ 21 декабря 2009

Редактировать: , как указывает ирония, это не относится к случаю, который вы опубликовали. Я оставляю его без изменений, так как это может быть полезно в других ситуациях.

Когда вы вызываете AddToChildren (), вы получаете реализацию A, которая (конечно) добавляет статический член A.

Это потому, что в C ++ нет понятия «виртуальные данные». Одним из способов было бы добавить виртуальную функцию GetList (). I A это выглядит так (непроверенный код):

virtual vector <a*> * GetList() {
   return & A::children;
}

и в B:

virtual vector <a*> * GetList() {
   return & B::children;
}

Затем измените AddToChildren на:

void A::AddToChildren(A *obj)
{
    GetList()->push_back(obj);
}
3 голосов
/ 21 декабря 2009

Код функции применяется только к классу, в котором она определена, даже если она виртуальная. Итак, следующая функция всегда применяется к классу A и поэтому добавляется к A::children:

void A::AddToChildren(A *obj)
{
  children.push_back(obj);
}

Если вам нужны разные векторы для каждого класса, у вас нет другого выбора, кроме как повторять код из одного класса в другой (статическая переменная, ее инициализация и функция add-to-children или функция get-children-list ). Я бы не советовал вызывать виртуальные функции в конструкторах.

Другой подход, который может вас заинтересовать, - это создать отдельный шаблон класса для такого хранилища:

template<typename T> struct children 
{
  static std::vector<T*> list;
  static void add(T* t) { list.push_back(t); }
};

B::B() : A() {
  children<A>::add(this);
}

C::C() : B() {
  children<B>::add(this);
}
1 голос
/ 21 декабря 2009

Ваш замысел недостаточно ясен, поэтому авторы других ответов запутались в своих ответах.

В вашем коде вы, кажется, делаете вызовы AddToChildren от одних конструкторов, но не от других. Например, у вас есть список children в A, но вы никогда не вызываете конструктор AddToChildren из A::A. Кроме того, у класса C нет собственного списка children. Зачем? Предполагается ли поделиться списком children с B?

Я могу предположить, что тот факт, что вы не вызываете AddToChildren из всех конструкторов, означает, что некоторые конструкторы предназначены для создания полных "объектов" данного типа (эти конструкторы действительно вызывают AddToChildren), в то время как некоторые другие конструкторы предназначен для использования в качестве «промежуточных» конструкторов классами-потомками (эти конструкторы не вызывают AddToChildren).

Такой дизайн может показаться весьма сомнительным и подверженным ошибкам. Обратите внимание, например, что C::C вызывает AddToChildren, который предположительно добавляет this к B::children (было ли это намерением?), А также вызывает конструктор B::B, который также добавит this к B::children. Таким образом, одно и то же значение this добавляется в список дважды. Это, кажется, не имеет никакого смысла.

Вам нужно выяснить, что именно вы пытаетесь сделать, а затем исправить свой дизайн. Как только вы закончите с этим, вы можете «виртуализировать» список, используя технику, предложенную Нилом (представляя виртуальный метод GetList). Нил позже написал неправильно, что это не будет работать. На самом деле, он будет отлично работать (опять же, при условии, что я правильно понял ваш замысел).


(с учетом уточняющих комментариев ФП)

Итак, вы хотите, чтобы B объекты добавлялись в список A::children, а C объекты добавлялись в списки A::children и B::children. Это может быть достигнуто с помощью

class A {
  ...
  int a;
  static vector<A*> children;
  ...
  A(int a) : a(a) {}

  virtual vector<A*> *GetList() = 0;

  void AddToChildren(A* obj) { // note: non-virtual
    GetList()->push_back(obj);
  }
  ...
};

class B : public A {
  ...
  int b;
  static vector<A*> children;
  ...
  B(int a, int b) : b(b), A(a) { 
    AddToChildren(this);
  }

  virtual vector<A*> *GetList() {
    return &A::children;
  }
  ...
};

class C : public B {
  ...
  int c;
  ...
  C(int a, int b, int c) : c(c), B(a,b) { 
    AddToChildren(this);
  };    

  virtual vector<A*> *GetList() {
    return &B::children;
  }
  ...
};

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

Кроме того, все это мало что дает, если AddToChildren просто делает push_back. Нет особого смысла строить такую ​​инфраструктуру только для таких "тонких" AddToChildren. Просто делайте то, что вы хотите сделать явно в каждом конструкторе.

0 голосов
/ 21 декабря 2009

Вы не показали нам реализацию A::AddToChildren или B::AddToChildren, но именно в этом проблема. Так или иначе, ваша реализация B::AddToChildren добавляет неправильный вектор. Может быть, вы не специализировали метод для B? Не могу сказать, не увидев эту часть кода.

Редактировать после комментариев : Если вы настаиваете на использовании здесь наследования, вы могли бы сделать что-то вроде:

class A
{
    ...
    virtual vector<A*> * ChildrenPtr();
};

...

A::ChildrenPtr() {return &A::children;}
B::ChildrenPtr() {return &B::children;}
C::ChildrenPtr() {return NULL;}

void A::AddToChildren(A *obj)
{
    vector<A*>  * pChildren = ChildrenPtr();
    if (pChildren)
        pChildren->push_back(obj);
}

В этом случае я, честно говоря, думаю, что это более запутанно, но не меньше.

Это очень похоже на то, что сказал Нил Баттерворт выше, но также немного безопасен для любого, кто когда-либо ссылался на C :: AddToChildren (A * obj).

0 голосов
/ 21 декабря 2009

@ Нели: Такой подход не решит эту конкретную проблему, потому что в C ++ вызовы виртуальных функций от деструкторов / конструкторов фактически не являются виртуальными. Так что из конструктора GetList () все равно будет возвращать потомков А.

P.s. это должно быть в комментарии к ответу, но я не смог найти подходящий вариант ..

P.P.S. Специально для AndreyT с любовью

Пожалуйста, внимательно прочитайте следующее. Это от международного стандарта C ++, 12.7.

Когда виртуальная функция вызывается прямо или косвенно из конструктора (в том числе из mem-initializer для члена данных) или из деструктора, а объект, к которому применяется вызов, является объектом, находящимся в процессе создания или уничтожения, функция Вызывается тот, который определен в собственном классе конструктора или деструктора или в одной из его баз, но не функция, переопределяющая его в классе, производном от класса конструктора или деструктора, или переопределяющая его в одном из других базовых классов наиболее производного объект (1.8). Если вызов виртуальной функции использует явный доступ к члену класса (5.2.5), а выражение объекта ссылается на объект, находящийся в процессе создания или уничтожения, но его тип не является ни собственным классом конструктора, ни деструктора, ни одной из его баз, результат звонок не определен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...