[Ns-developers] A question about downcast netdevice pointer.
craigdo@ee.washington.edu
craigdo at ee.washington.edu
Thu Sep 25 12:10:06 PDT 2008
> > "aggregated" seems there are two objects aggregated together. Why
> > there is CsmaNetDevice object aggregated with a NetDevice object?
> >
> > I went throught the code, it seems there is only one object
> > (CsmaNetDevice) for each node.
> >
> > Am I right?
>
> You are right, there is only one object.
>
> I for one find using GetObject<SubClass>() for downcasts less
> intuitive than
> dynamic_cast, but I am aware some other developers don't
> quite agree with me
> for whatever reason.
A little history may help to understand what is happening here.
By design there are two distinct ways one can look at this whole object
aggregation idea. We went through a long series of arguments regarding
whether or not to allow object-like behavior or to call these things
Interfaces and only allow COM-like behavior. The end result is a unique
system that you can view as a complexified object system or a simplified COM
system. What you're running into now is the blurry line where those two
models meet.
When I wrote the initial documentation on this, I was fond of quoting
Richard Feynman's (American physicist) comments about wave-particle duality.
What we have here is an interface-object duality; and different people look
at it in different ways at different times, just like physicists view
photons, for example.
If you look at the CsmaNetDevice as a collection of Interfaces, you'll see
that there is an Object Interface, a NetDevice Interface, and a
CsmaNetDevice Interface involved. If you don't concern yourself with how
these interfaces are implemented, the COM-like Interface model makes perfect
sense. In that case, you wouldn't ever consider doing a dynamic cast to get
from one Interface to another since they may be implemented in completely
different ways. You would do an interface query. If you neglect the
implementation and accept for a moment that QueryInterface is spelled
GetObject in our system, the GetObject makes good sense:
Ptr<CsmaNetDevice> nd = dev->GetObject<CsmaNetDevice> ();
In this conceptual model, you have a pointer to an Interface in an
aggregation, and you're doing a QueryInterface equivalent on the aggregation
looking for the CsmaNetDevice Interface. It doesn't matter how that
aggregation is implemented at all. I personally look at things this way,
and it makes perfect sense for me to move around in the object hierarchy
just like this. I don't have to care about how the underlying object author
decided to implement the Interfaces. If someone aggregates another Object
to the CsmaNetDevice, I get to it in the same way and I don't have to care
about how that new object was implemented (through inheritance or
aggregation).
Now, if you are looking at the situation from the perspective of the
implementer of the CsmaNetDevice, the picture changes. In this case, you
know you have implemented the CsmaNetDevice as an object that inherits from
NetDevice which, in turn, inherits from Object. If you look at the world
this way, you'll tend to think of doing a downcast to move from a NetDevice
Ptr to a CsmaNetDevice Ptr. In this case, Gustavo's DynamicCast makes good
sense:
Ptr<CsmaNetDevice> nd = DynamicCast<CsmaNetDevice> (dev);
In this particular case, the two approaches accomplish the same thing and we
have previously decided that developers can choose to look at the situation
in whatever way they want.
Regarding that choice, we went around and around about whether or not to
enforce one view or another on our users and we ended up deciding that it
was all a matter of taste which world-view you found more appealing. We
considered a more dictatorial approach in which all Interfaces (in the COM
sense) were broken out and it was made excruciatingly obvious what things
were public Interfaces and how they were implemented. In that case, it
would've been obvious that when you put together a CsmaNetDevice you would
have a CsmaNetDevice Interface that inherited from Object, and a NetDevice
Interface that also inherited from Object; and these two Interfaces would've
been explicitly aggregated even though they may have been implemented via
inheritance. The trade-off was explicit construction, and ease of
documentation and understanding, versus having to write all of the code in
all of the files to implement all of this. In the explicit approach you
would have had pure virtual Interface declaration headers with corresponding
implementations that were explicitly aggregated together.
Another approach was to make much of this implicit; and allow us to build
devices in the normal C++ way. We made existing class hierarchies work in
the object aggregation setting. You could build a CsmaNetDevice object that
inherited from NetDevice and Object, and present them to the world as an
aggregation of those three Interfaces without having to write all of the
interface code. You could also treat this class hierarchy as you would in a
normal C++ situation. This made backfitting the Object system into an
existing ns-3 easy and resulted in less required code to implement
interfaces (there were other plusses and minuses talked about as well, even
down to how source files were displayed on your screen when you typed 'ls').
As has been observed, there can be confusion since this is done implicitly.
Even more confusion can happen when you introduce implementation classes --
classes that implement Interfaces/Objects but that have no type and aren't
really first-class Objects with Interfaces but are accessible by dynamic
cast. The main problem in using this Object-as-Interface approach, as has
been recently rediscovered, is that there is no easy way to figure out which
"Interfaces" are exported -- you get to read the source code and know how
the object model works.
The problem is exacerbated by naming. We decided that pure virtual
Interfaces in the COM sense aren't what we deal with -- we are talking about
Objects which are really C++ objects. We originally used QueryInterface as
the name for the interface discovery mechanism. We changed to QueryObject
when we changed the emphasis to Object from Interface. After much
"discussion" we decided it was important to convey that we were getting a
pointer to an underlying C++ object, not some other special kind of thing.
The name ended up being GetObject. Of course, when we did this, we lost the
association with COM that kindof held the whole model together conceptually.
We voted for flexibility over understandability in an explicit choice in
this case.
We were going to address the confusion by documentation, but that seems to
have fallen by the wayside. You can find a slightly out-of-date version of
my attempt to convey all of this in ns-3-dev/doc/manual/other.texi
(understand that it *is* out-of-date). Look for the "Object Model" section.
-- Craig
More information about the Ns-developers
mailing list