views:

310

answers:

5

Hello,

I need to keep a list(vector) of children for every class, and I've got a tricky problem:

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

Adding A(0), B(1,1) and C(2,2,2) and calling a.ShowChildren gives: 1,1 ; 2,2,2 ; 2,2,2

Every time I make an instance of class C the A::children is updated instead of B::children and A::children. Sooo... the class C is added twice to the children of A class, but not added to class B. It helps when I copy the AddChildren class (literally copy) to class B, so that every class has its own AddChildren/ShowChildren. Also I've managed to accomplish this task using pointers, but I'm wondering is there a better way. I think that the problem is somewhere in the "using the right vector", but I don't know how to force the compiler to use the right one.

I would be grateful for any suggestions on whatever I'm doing wrong here.

First of all, thank you all for your comments and help. Using your advice (about my design and virtual GetList()) I managed to simplify my program:

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

Now its using constructors without calling the upper class, it just calls the proper constructor of the upper class. Its not the best, but i think it's better.

Once more, thank you all for help.

+4  A: 

Edit: as ironic points out, this does not apply in the case you posted. I'm leaving it undeleted as it may be useful in other situations.

When you call AddToChildren(), you get A's implementation, which (of course) adds to A's static member.

This is because C++ has no concept of "virtual data". One way round it would be to add a virtual function called GetList(). I A it looks like this (untested code):

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

and in B:

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

Then change AddToChildren to:

void A::AddToChildren(A *obj)
{
    GetList()->push_back(obj);
}
anon
@Neil: I don't know why you are bashing your own answer. Ironic's remark is completely incorrect. The OP wants to have the pointers added to `B::children` when `B` is constructed. That's exactly how it will work in your implementation. Virtual calls made from construtor *are* virtual in C++ (contrary to some strange popular but incorret belief). They simply work "down to the level" of the class whose constructor is currently working. The OP calls `AddToChildren` from `B::B`. In this case the virtual call will resolve to `B::GetList`, as intended.
AndreyT
@Andrey: virtual functions in constructors do not act like they are virtual. Because of this we say they are broken. It turns out that "down to the level" has the exact same semantics as "treated as if the function wasn't virtual".
caspin
@Caspin: Incorrect. You seem to be a victim of the same popular, but incorrect belief. Virtual functions called from constructors (directly or indirectly) *do* act like virtual. Except that the "depth" of their "virtuality" is limited by the type of the object whose constructor is currently active. This "amount of virtuality", albeit limited, is prefectly sufficient to implement the functionality requested by the OP.
AndreyT
@Andrey: But that's not virtual function behavior any more, right? That's the same behavior as a non-virtual.
@jeffdanger: No, it is not. It will look as "non-virtual" only if you call the function from the constrcutor *directly*. But when inside the constructor you call another function from some parent (like `AddToChildren` in this case) and then that function calls another virtual function (`AddToChildren` calls `GetList`), the behavior is obviously *virtual*.
AndreyT
+3  A: 

The code of a function only applies to the class it is defined in, even if it is virtual. So, the following function always applied to class A and therefore appends to A::children :

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

If you want distinct vectors for every class, you have no choice but to repeat code from one class to another (the static variable, its initialization, and either an add-to-children function or a get-children-list function). I would advise against calling virtual functions in constructors, though.

Another approach you might be interested in would be to have a distinct class template for such storage:

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);
}
Victor Nicollet
I see. The template solution looks nice, but since its a project at the university I would rather like to keep the solution in one place.
M_F
You could always make the struct template a member of A.
Victor Nicollet
A: 

@Neli: Such approach would not solve this concrete problem because in C++ calls to virtual functions from destructors/constructors are actually not virtual. So from the constructor GetList() will still return children of A.

P.s. this should be in comment to the reply, but I could not find appropriate option..

P.P.S. Specially for AndreyT with love

Please read the following carefully. It is from C++ international standard, 12.7.

When a virtual function is called directly or indirectly from a constructor (including from the mem-initializer for a data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor’s own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor’s class, or overriding it in one of the other base classes of the most derived object (1.8). If the virtual function call uses an explicit class member access (5.2.5) and the object-expression refers to the object under construction or destruction but its type is neither the constructor or destructor’s own class or one of its bases, the result of the call is undefined.

ironic
Whups, I missed that he was calling the code from the ctor/dtor - you are quite right.
anon
In C++ calls to virtual functions from constructors/destructors are as virtual as from anywhere else. The only difference is that the dynamic type of the object is set to be the type, whose constrcutor is currently working. If your compiler translates these calls as non-virtual (makes sense), it is nothing that just a compiler-specific optimization, which has no relation to C++ language in general.
AndreyT
In Neil's approach everything will work as intended: When `B` is constructed, `A::AddToChildren` will invoke `B::GetList`, because the call *is* virtual.
AndreyT
@Neil: Neil, what are you takling about? All you need is that when `B::B` constructor is working the call to `GetList` should resolve to `B::GetList`. That's exactly how it will work. Ironic's remark is completely incorrect.
AndreyT
@AndreyT: Please see my edited reply
ironic
@ironic: The standard says is exactly what I'm saying. Please, read the standard more carefully. You used a meaningless term "non-virtual". Now, could you find it in the quited text? No. Why? Because it isn't there. So, instead of parroting some naive urban legends about non-virtual behavior, read the standard, place.
AndreyT
@ironic: The text in the standard clearly and explicitly states that virtual functions always act like virtual functions and the calls are always virtual. The only difference, the standard says in your quote, is that when it is called from a constructor, the concrete function is selected from the "restricted" hierarchy: from the root to the class whose constructor is currently working. This is not "non-virtual". This is virtual with "restricted depth", so to say. Try playing with it in actual code (see examples in other answers), and you'll get it eventually.
AndreyT
@ironic: This "restricted depth" virtuality is perfectly sufficient (or, more precisely, exactly what is needed) in order to implement the functionality requested by the original poster. What Neil suggested works perfectly fine.
AndreyT
@ironic: P.S. I don't know who and how hammers that "calls from constructor are non-virtual" nonsense into people's heads, but they certainly do it well. It is very hard to make people see what is really happening when a virtual function is called from the constructor. But you are the first to quote a piece of standard to me that supports *my* assertions, and present it as if it supports *yours*. Have you actually tried to read it?
AndreyT
@ironic: FInally here is a link to C++ FAQ for you: http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.5 . Try reading it carefully and think about why it never says that the calls are *non-virtual*, but says instead that when `Base` constructior is working the calls are resolved as if the object under construtions is `Base`, not `Derived`. Do you get the implications and the difference? Again, play with the code, read the FAQ. Then read the standard, maybe you'll finally be able to understand what it really says. If not, get back to me, we'll work it out.
AndreyT
@AndreyT: Thanks for your desire to make everything completely clear. First, I am completely agree about the "restricted depth". Second, actually, I did not pay enough attention to the question. Now, when I did, I understand that it is different from situations that I find typical. I missed the point, that the aim is not to add children to B::children in A::A(), but to add children into A::children in A::A(), and B::children in B::B(). So one more time thanks for attention and sorry for the mess.
ironic
A: 

You haven't show us the implementation of A::AddToChildren or B::AddToChildren, but that's where the problem will be. One or another way, your implementation of B::AddToChildren is adding to the wrong vector. Maybe you didn't specialize the method for B? Can't really tell without seeing that part of the code.

Edit after comments: If you insist on using inheritance here, you could do something like:

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

In this case, I frankly think that's more confusing, though, not less.

This is very similar to what Neil Butterworth said above, but also has a bit of safety about anyone ever invoking C::AddToChildren(A *obj).

Joe Mabel
I wanted to avoid copying the A::AddToChildren to B::AddToChildren. It solved the problem, but I'm looking for a more... elegant solution. So the B::AddToChildren is inherited from A::AddToChildren.
M_F
The code might be verbatim identical, but they access different static vectors, even though both vectors have the same name (give or take the qualifying class). Templating is the only way around duplicating the code: the names may be identical, but the variables (the vector) are not. Templating would avoid duplication of text, but still generates the same code.
Joe Mabel
A: 

Your design intent is not sufficiently clear, which is why the authors of some other answers got confused in their replies.

In your code you seem to make calls to AddToChildren from some constructors but not from the others. For example, you have a children list in A but you never call the AddToChildren from A::A constructor. Also, class C has no its own children list. Why? Is it supposed to share the children list with B?

I can guess that the fact that you are not calling AddToChildren from all constructors means that some constructors are intended to build complete "objects" of given type (these constructors do call AddToChildren), while some other constructors are intended to be used as "intermediate" constructors by descendant classes (these constructors don't call AddToChildren).

Such design might be considered quite questionable and error prone. Note, for example, that C::C calls AddToChildren, which supposedly is adding this to B::children (was it the intent?), and also invokes B::B constructor, which will also add this to B::children. So, the same this value is added to the list twice. This does not seem to make any sense.

You need to figure out what is it you are trying to do and then fix your design. Once you are done with it, you can "virtualize" the list using the technique proposed by Neil (introducing a virtual GetList method). Neil later wrote incorrectly that it will not work. In fact, it will work perfectly fine (again, assuming that I understand your intended design correctly).


(Taking into account the OP's clarifying comments)

So, you want B objects to be added to A::children list and C objects to be added to both A::children and B::children lists. This can be achieved by

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

Note that despite what was said by other posters, virtual calls do work here and they work exactly as we need them to work to achieve the requested functionality. Note though, that in this case there's no point to make method AddToChildren virtual, the virtuality of GetList alone is sufficient.

Also, the whole thing makes little if AddToChildren just does a push_back. There's no much sense the build such infrastructure for such a "thin" AddToChildren alone. Just do what you want to do explicitly in each constructor.

AndreyT
Hmm, it may seem a little unclear. C doesn't have a children list because its a "leaf", its the bottom class. The intention was that every descendant class adds herself to the "Children" lists of all class that it inherits. So C should be added to A::children and B::children.In the question there is an updated version, I made my design work whit Neil's proposition. I think you're right about my class design. Changing it made the thing a little simpler (but still not the best I think).
M_F