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