Thread View: comp.std.c++
3 messages
3 total messages
Started by mfinney
Fri, 22 Dec 1995 05:57
Friend & dual class heirarchies
Author: mfinney
Date: Fri, 22 Dec 1995 05:57
Date: Fri, 22 Dec 1995 05:57
62 lines
2339 bytes
2339 bytes
I keep running into the problem where I have dual class heirarchies
and need to specify friend in one for the other. However, since
friend is not inherited every time a class is subclassed it is necessary
to specify yet another friend. For example, suppose I have...
struct BinaryNode {
friend BinaryTree;
friend AvlTree;
...};
struct AvlNode {
friend AvlTree;
...};
struct BinaryTree {...};
struct AvlTree : BinaryTree {...};
If I add...
struct RankedAvlNode : AvlNode {
friend RankedAvlTree;
...};
struct RankedAvlTree : AvlTree {...};
then it is necessary to add new friend statements to BinaryNode
and to AvlNode.
The problem is that friend is designed to support those situations
where classes are tightly coupled for some reason, such as a
collection and its iterator. However, with the greater use of
frameworks and design patterns, tightly coupled dual class
heirarchies are becoming more and more common. Even in the
collection and iterator example, it is more likely to be parallel
class heirarchies rather than simply two classes.
All the usual reasons for protected/private data members and
functions apply as well as the reasons for using friend. While
I certainly don't think that friend should be used often, its use
is essential for good design in many situations -- especially when
building frameworks.
Does the draft standard address this issue, and if not should a
variant of friend be defined. Say...
class BinaryNode {
friends BinaryTree;
...};
would solve the problem quite nicely. This is a major problem
in keeping a clean design for larger systems.
Michael Lee Finney
---
[ comp.std.c++ is moderated. Submission address: std-c++@ncar.ucar.edu.
Contact address: std-c++-request@ncar.ucar.edu. The moderation policy
is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]
Re: Friend & dual class heirarchies
Author: John Max Skaller
Date: Sat, 30 Dec 1995 01:30
Date: Sat, 30 Dec 1995 01:30
151 lines
6051 bytes
6051 bytes
mfinney@inmind.com wrote:
>
>I keep running into the problem where I have dual class heirarchies
>and need to specify friend in one for the other.
> For example, suppose I have...
>
> struct BinaryNode {
> friend BinaryTree;
> friend AvlTree;
> ...};
>
> struct AvlNode {
> friend AvlTree;
> ...};
>
> struct BinaryTree {...};
> struct AvlTree : BinaryTree {...};
>
>If I add...
>
> struct RankedAvlNode : AvlNode {
> friend RankedAvlTree;
> ...};
>
> struct RankedAvlTree : AvlTree {...};
>
>then it is necessary to add new friend statements to BinaryNode
>and to AvlNode.
>
>The problem is that friend is designed to support those situations
>where classes are tightly coupled for some reason, such as a
>collection and its iterator. However, with the greater use of
>frameworks and design patterns, tightly coupled dual class
>heirarchies are becoming more and more common. .
>All the usual reasons for protected/private data members and
>functions apply ..
I guess the thing to do is be a bit suspicious of the design
technique. For example: why can the user see the node types
above at all?
An idea of how to re-engineer:
** Separate the interface and implementation **
The inteface classes don't need friend declarations
because there are no implementation details to be
friendly about :-)
The implementation classes can be structs, with public
data members, and no encapsulation at all. The interface
classes hide the details, but the implementation
classes can access other implementation classes without
friend declarations.
One method of doing this is to use indirection: a private
pointer to a struct. The user can pervert any
data they can lay their hands on .. but they can't
get their hands on it!
I have a similar kind of problem: I have several
kinds of invasive reference counting smart pointers: they
need access to a refcnt data member of the object
they manage. I make it public.
Now, this means the user can screw up the automatic
memory management. Is this bad?
I think it's the wrong question. The user has _asked_
a server (the smart pointer) to manage an object
_they_ created. Before that, they could already screw
up the memory management. By handing over to the smart
pointer, the user is asking for help -- the smart
pointer promises to help _provided_ the user doesn't
go and do something stupid (like manually delete the object).
But the user can still intervene and take control
back ... and may actually _need_ to fiddle the
reference count. For example .. by inventing
another kind of reference counting smart pointer!
With several kinds of smart pointers
the user may decide to change the management protocol
and may need to manually adjust the ref count
in the process of transfering responsibility.
The refcount could be made private by making the
smart pointer a friend. But then only the nominated
kind of smart pointer can manage the object.
I HAVE caused a crash by messing up invasive
smart pointers. But I didn't do it by writing
to the publically accessible refcount, I did it
by stealing the raw pointer and giving it
to a _non-invasive_ reference counted pointer --
which thought it had exlusive control and deleted
the object from under the nose of the counting
smart pointers.
In my opinion, encapsulation of objects is not quite right:
data structures and functions in an implemtation
module should have free access to other components
of the module. [Naturally, the user interface provides
only managed access].
That is, C++ and other "object" oriented systems
operate by determining that the granularity
of the module notion is the class -- which is too
fine. C++ provides friends to escape this design
compromise (which seems necessary for linear
polymorphism).
This suggests working with structs and global functions
in a space without any protection, (simulating
a module of coarser granularity) and then hiding
the implementation of the _system_ behind an
interface -- which may look nothing like the
representation.
If you consider STL, it is very much an unprotected,
unencapsulated system residing in the implementation domain.
To use it, it needs to be hidden
inside encapsulated objects of the problem domain.
So it may pay to be a bit sloppier about protection
of data structures .. since they're largely tools
for creating representations of abstractions,
and it is _these_ representations that need to be hidden,
rather than the details of the tools used to create them.
For example: if you have objects that should NOT be moved,
then you might put them in a list<>, which promises never to
move things. You would frown on a vector<>, because it
does tend to move things.
..tend to. But sometimes, you can reserve storage
in the vector, and then objects won't move. You can
rely on it if you know the implementation details.
--
John Max Skaller voice: 61-2-566-2189
81 Glebe Point Rd fax: 61-2-660-0850
GLEBE NSW 2037 web: http://www.maxtal.com.au/~skaller/
AUSTRALIA email: skaller@maxtal.com.au
---
[ comp.std.c++ is moderated. Submission address: std-c++@ncar.ucar.edu.
Contact address: std-c++-request@ncar.ucar.edu. The moderation policy
is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]
Re: Friend & dual class heirarchies
Author: mfinney
Date: Sat, 30 Dec 1995 20:58
Date: Sat, 30 Dec 1995 20:58
127 lines
6268 bytes
6268 bytes
In <4c17cf$k52@oznet03.ozemail.com.au>, John Max Skaller <maxtal@suphys.physics .su.oz.au> writes: >I guess the thing to do is be a bit suspicious of the design >technique. For example: why can the user see the node types >above at all? In this particular example, these are low level data structures which are used to implement higher level data structures. Those data structures hide everything. But due to efficiency concerns, using indirected pointers is not acceptable (actually, the node classes are not even virtual for space concerns). The node structure is intentionally exposed (since this is a low-level data structure) but only operations which can VIEW the structure are publically available. These operations are used by higher level code to create iterators, more sophisticated data structures, etc.. But that code has to see a certain level of detail. These structures are sufficiently strong that they can also be used directly by the user. The tradeoff is that one type of tree can be substituted for another type of tree, but not for, say, a hash table. Where using the high level structures will allow substitution by a hash table, but at a minor cost of efficiency (such as engendered by use of indirection for encapsulation). Most applications can afford such costs, but some cannot. So you need both types of data structures. But, even with the "high" level data structures, this type of problem repeats itself in the dual Collection/Iterator heirarchy. >An idea of how to re-engineer: > > ** Separate the interface and implementation ** As mentioned above, unacceptable due to efficiency considerations. >I have a similar kind of problem: I have several >kinds of invasive reference counting smart pointers: they >need access to a refcnt data member of the object >they manage. I make it public. > >Now, this means the user can screw up the automatic >memory management. Is this bad? Yes. If your "smart" pointers are derived from a common class then you do have the same problem. You need to declare it and its subclasses to be friends in a single declaration point and to make that data element private. Ideally, you would export JUST that single data element, but friendship is, unfortunately, not sufficiently fine-grained for that. >I think it's the wrong question. The user has _asked_ >a server (the smart pointer) to manage an object >_they_ created. Before that, they could already screw >up the memory management. By handing over to the smart >pointer, the user is asking for help -- the smart >pointer promises to help _provided_ the user doesn't >go and do something stupid (like manually delete the object). In a small system, perhaps that attitude is o.k. but in a larger system the need to write code "protectively" will disallow that approach. >But the user can still intervene and take control >back ... and may actually _need_ to fiddle the >reference count. For example .. by inventing >another kind of reference counting smart pointer! If that need is actually anticipated, then the data element should be protected instead of private. Or you have the situation where you need another "friend". The fundamental issue is that while ideally a class design can be fully decomposed, there are any number of examples which REQUIRE less than full decomposition. And the C++ way of providing for that is "friend". Some of those examples fall into the area where just a single unction needs to be a friend. And, barring mutually dependent definitions, C++ allows for that. There are cases where just a single class needs to be a friend. And C++ allows for that. But there are also cases, especially when building frameworks, where an entire class heirarchy needs to be a friend. And C++ does not allow for that. Which is why I suggested that "friends" be introduced to allow for the lack. I would ALSO like to see friendship be more fine grained so that I can export just a particular data element at the particular privledge level that I desire. Perhaps I don't want EVERYTHING to be shared, just a particular component. Perhaps I don't want the private information to be inherited if using a "friends" statement. >In my opinion, encapsulation of objects is not quite right: >data structures and functions in an implemtation >module should have free access to other components >of the module. [Naturally, the user interface provides >only managed access]. I disagree with this. >That is, C++ and other "object" oriented systems >operate by determining that the granularity >of the module notion is the class -- which is too >fine. C++ provides friends to escape this design >compromise (which seems necessary for linear >polymorphism). I will admit that a "larger" encapsulation mechanism could be useful. But even within such a mechanism I would want to use friendship to carefully dole out the ability to interact. >So it may pay to be a bit sloppier about protection >of data structures .. since they're largely tools >for creating representations of abstractions, >and it is _these_ representations that need to be hidden, >rather than the details of the tools used to create them. Again, I disagree here. I want the strongest encapsulation I can manage to strictly control access to data element and methods. Michael Lee Finney --- [ comp.std.c++ is moderated. Submission address: std-c++@ncar.ucar.edu. Contact address: std-c++-request@ncar.ucar.edu. The moderation policy is summarized in http://dogbert.lbl.gov/~matt/std-c++/policy.html. ]
Thread Navigation
This is a paginated view of messages in the thread with full content displayed inline.
Messages are displayed in chronological order, with the original post highlighted in green.
Use pagination controls to navigate through all messages in large threads.
Back to All Threads