RE: [Doxygen-users] Class collaboration [LONG]
Brought to you by:
dimitri
From: Anthony Y. <an...@ac...> - 2001-09-14 14:26:09
|
Let me take the discussion a little further since I started it... A fair warning, it is a bit long... [snip] > "Should only direct relations be considered in the > collaboration diagram?" My answer to this question is YES. But it would be helpful to define exactly what "direct relation" is. I'll quote from the "graph legend": "A purple dashed arrow is used if a class is contained or used by another class. The arrow is labeled with the variable(s) through which the pointed class or struct is accessible." So, in fact, keep in mind that Doxygen already considers (plain) pointers to another class as a collaboration. This means the "level of indirect relation" is 2 (see below). Then why does Doxygen put a "purple dashed arrow" if there is a "pointed class" inside another class? Let's examine a possible reason for that. It is clear what "a class is contained" means. It means that: if class B has an instance member variable to class A, then we say "class A is contained in B". Should a pointer to class A count as "contained in" too? No, I don't think so. Even if sizeof(A) changes, sizeof(B) doesn't, because B only contains a pointer (or reference). This is a proof that class A is *not* contained in class B. DEFINITION: Class A is CONTAINED in class B if and only if class B has an instance (not pointer or reference) member variable to class A. This leaves us the second way to get a "purple dashed arrow", which is when class A is "used" by class B. But what is "used"? How do you define "used"? Here is a reasonable definition: DEFINITION: Class A is USED-BY class B if and only if in some member functions of class B invokes a member function of class A. (I say member function, because we make all our member variables private, right?) Obviously, this includes (public, protected, private) inheritance relationships, since a derived class, at the minimum, must invoke the base class constructor(s) and destructor. Doxygen handles these separately. Exploring further, we see that if class B invokes a public member function of class A, then class A is USED-BY class B. How is this possible? There are 3 ways in C++: instance, pointer, reference. struct A { void Foo(void){} }; CASE I (Instance) 1) class B { A a; void Bar(void) {a.Foo();} }; 2) class B { A a[10]; void Bar(void) {a[0].Foo();} }; // ?? 3) class B { void Bar(A a) {a.Foo();} }; CASE II (Pointer) 1) class B { A *a; void Bar(void) {a->Foo();} }; 2) class B { void Bar(A *a) {a->Foo();} }; CASE III (Reference) 1) class B { A &a; void Bar(void) {a.Foo();} }; 2) class B { void Bar(A &a) {a.Foo();} }; We need to be careful in handling cases II and III, because B does not necessarily invoke any public member function of A... However, in reality, Doxygen simply concludes that class A is USED-BY Class B in cases II.1 and III.1 It does not consider II.2 or III.2 Perhaps it should? Should a friend class be considered a collaboration? Sure. Otherwise, the two classes shouldn't be friends in the first place. The reason you made class B a FRIEND to class A is so that B can invoke protected or private member functions of A... Doxygen does not handle this case either. In conclusion, what is a "direct relation"? DEFINITION: Class A is DIRECTLY-RELATED to class B if and only if class A is CONTAINED in class B, or class A is USED-BY class B, or class B is a FRIEND to class A. The above is my humble opinion on what a "direct relation" is. It is by no means exhaustive or scientifically rigorous. >If the answer is no, then main() (for example) is a >function that is indirectly related to almost everything >in the program -- except the part hidden in >implementation parts (.c, .cpp). If I use here some >object of the class that hides inside the whole >functionality of the application, then the collaboration graph >of that class would be very complex and it would contain almost >every class of the application. (Well, this is rather. >simplified, but I hope you understand the point.) Yes, I see a problem here, if the "level of indirect Relation" is allowed to be "infinity". >If the above were accepted, then the only reasonable >solution would be to define the maximum level of indirect >relations. Say, 1 (one) is the level when we think only >about direct relations. Agreed, if you consider my definition of a direct relation. >Now, std::vector<A> is a vector of instances of class A. >The mentioned example of std::vector<A *> is vector of >pointers to such instances. Even when you know how >std::vector<whatever> may look like inside---from the abstract >point of view---you should agree that the "whatever" is hidden >inside something lager. If "vec" is the object of that class, then >"whatever" is somehow hidden inside. So, I would mark >the level of collaboration between B and "whatever" using >at least value 2. Moreover, if the "whatever" is a >pointer to "something-else", I would mark the level of >collaboration between B and "something-else" using at >least the value 3. I agree that "whatever" is hidden. But if class B invokes a public member function of class A, through the vector or some other kind of container, then class A is USED-BY class B. Another way of "justifying" a collaboration between A and B is that, std::vector<A> (or std::vector<A *>) is a type that depends on A. So A is USED-BY B to create a new type. Hence, we have a CASE IV: CASE IV (Template) 1) template <typename T> class B {}; B<A> b; 2) class B { template <typename T> void Bar(void){} }; B b; b.Bar<A>(); 3) class B { some_template<A> t_a; }; Of course, there are many variations on this theme, but I hope you get the idea. This CASE IV is what complicates things by many orders of magnitudes. And I wouldn't want to be the person to try to solve it. :) >Even more serious problem is that the argument of any >template is used textually inside the body of the >template. This also means that you do not exactly know >how it is used. We know how it is used -- to create a new type using the template! > To conclude, I guess that generally it would be very >difficult to build the collaboration diagram correctly if >the template arguments should be followed. Agreed. May be this is an NP-Hard problem. :) >On the other hand, the life is not only that much >scientific. It could probably be useful to include some >internal knowledge about standard containers into doxygen. >But then, you have to know whether "vector<something>" >means _some_ vector or std::vector<>, etc. In other words, >the parser would be probably more complicated. Agreed, so I "forgive" Doxygen for not handling the CASE IV. But, still, what about CASE II.2 and III.2? >So, the final question is: > > "Is the result of a solution of the problem worth of the > complexity of the solution?" Probably not... Oh well, life is full of tradeoff's. :) It has been fun typing this message. Thank you for your time if you made it this far. |