Re: Raw pointers not evil after all?
On Thursday, 2 May 2013 22:53:40 UTC+1, Christopher Pisz wrote:
On 5/2/2013 12:17 PM, James Kanze wrote:
On Thursday, 2 May 2013 02:03:00 UTC+1, Christopher Pisz wrote:
[...]
The owner should be the class that allocated the
object.
If the owner should be the class which allocated it, why bother
with dynamic allocation.
Usually, because
1) Size is unknown
That's handled in the few exceptions. Once you've got
std::vector and the like, how many other classes need to worry
about managing the memory of there data sets.
2) It is an an interface rather than concrete
Why is it an interface? Interfaces are normally used for entity
objects, and an entity object almost certainly won't be owned by
the class which allocated it.
3) Storing, passing, and manipulating pointers is more optimal than
copying a large object by value and references weren't an option.
When the profiler says you have to...
There are some exceptions, when
a class implements a dynamically sized structure (like
`std::vector`), but most of the time, the reason you're
allocating dynamically is that the object has an arbitrary
lifetime of its own, which may excede that of the object which
creates it.
I haven't found that to be the 'usual' case.
So what is the usual case for dynamic allocation. If the
lifetime corresponds to scope, you don't allocate dynamically.
If the object doesn't have identity, and can be copied, you
don't allocate dynamically (unless the profiler says you have
to). The most common reason for allocating dynamically that
I've seen is that the object has identity, and that it's
lifetime is arbitrary, and cannot be linked to the lifetime of
any other object. Otherwise, I'd make it part of the other
object, and be done with it.
I'd need a specific
example, but I try my best to make sure that any allocated object
is at least conceptually owned by someone if not really owned by
someone. No rule of the language or any textbook is going to say one way
or the other.
Perhaps, my real meaning behind the word "owned" is more abstract than
your meaning? We'd probably have to talk about specific examples for
clear communication.
It's true that "owned" has no real meaning in this context.
And why this insistence on ownership. In many application, the
most important objects aren't (or shouldn't be) owned by anyone.
Maintenence, readability, all those words people can argue about.
I am curious as to why you would say, "shouldn't be owned by anyone"
Why not?
Encapsulation. The object is autonomous, and is responsible for
its own behavior. Including its own lifetime. The object is
created as a result of some external event (data read, incoming
connection, etc.---but also something that happens elsewhere in
the program). Similarly, it will be destructed because of some
external event. Typically, which it processes in one of its
member functions. In the absense of transactions, most deletes
will be "delete this". (If there are transactions, the object
cannot simply delete itself, because this could make rollback
impossible. In such cases, it will request the transaction
manager to delete it as a result of commit.)
There's no special owner; the object assumes, and takes care of
itself.
The object itself manages its lifetime, in response to whatever
external events it waits one. (Who "owns" the window object in
a GUI application?) Or the objects are created by some sort of
factory (which certainly doesn't "own" them).
In native C++ applications that use the Windows API, I personally have
an Application class whom creates the windows, has a member that is the
Window procedure, and members that the Window makes use of, including
the handle itself. So, my Application class owns the Window. In turn,
the main function owns the Application instance. The Application class
instance owns and manages the lifetime of the Window.
So you add extra complexity, reducing reliability and
readability. I can see that there is a sense, here, that the
Window (and everything else) is owned by the Application, but
once it has been created, the Window should be fairly
autonomous, with the Application as an observer.
But in the case of a GUI, you're sort of right. There is
a containment hierarchy, and it does seem reasonable that
subobjects are destructed by the containing object: if a pane is
destructed, it deletes any components it may contain. I've
always categorized this as containment, rather than ownership,
but I guess either word could apply.
On the other hand, when you click on the X in the upper right
hand corner of the window, it is the window which receives the
event, and the window which decides what to do with it (which
should always be to delete itself, and everything in it).
Various parts of the application will be observers of the
window's components, and react in consequence; the application
itself will probably be an observer as well, so that when the
last window is destructed, the application shuts down.
In the factory example, I'd say take my meaning of "owned" less
literally. Some called a Create or some such method on the factory, got
back and instance to an object, and either owns it, or is going to
promptly give it to someone to own. When I say, "own" I mean that when I
am debugging, I know where it came from and whom is going to release it
when, it doesn't just float around where the programmer "hopes" it gets
released when everyone is done with it at some unknown point in time.
It's not a question of hope. The object receives certain
messages (events, etc.), and reacts to them. The object itself
"autodestructs" when it decides, according to its internal
logic. Other parts of the program don't know about this logic.
void Foo()
{
boost::shared_ptr<IMyInterface> someInterface = MyFactory.Create(1,
2, 3);
try
{
someInterface->DoStuff();
}
catch(common::BaseException & e)
{
m_logger(e.Message());
}
}
In the above, Foo owns it, its lifetime is clear, it will be destroyed
when an exception occurs and the stack unwinds or the function returns.
Yes, but in the above, the only reason you might possible use
a factory and allocate dynamically is that the object is
polymorphic. And such objects generally need to live longer
than the function. The function is called as a result of some
external event (say a request received on a socket interface).
Otherwise, there's no point in using an interface. If you're
worried about exposing too much of the implementation, there's
always the compilation firewall idiom. In which case, yes: the
implementation class definitely has an owner. But generally
speaking, if the object is going to cease being at the end of
the function, it should be local, and not dynamic.
What I don't like, and again this is my opinion and isn't in any textbook:
void Foo()
{
boost::shared_ptr<IMyInterface> someInterface = MyFactory::Create(1,
2, 3);
global_a.m_interface = someInterface;
global_b.m_interface = someInterface;
global_c.m_interface = someInterface;
}
Now, it is unclear when the instance of someInterface will be destroyed.
The author just depended on the reference counting to take care of it
for him and ignored the lifetimes of a, b, and c, probably didn't check
for cyclic references, and probably didn't check what will happen at
program exit. Not sure if those examples are the best to try and get
what I am trying to communicate across....
I think we may be closer to agreement than it seems. This is
precisely the sort of thing I'm complaining about. A simple
example: a network management system has something called an
EventForwardingDiscriminator, which is responsible for
forwarding various events to clients, so the clients can react
to them. (A client might be an interactive system displaying
network status, or some sort of security system which would
automatically reroute connections if there were problems on my
system.) The connection receives a request from the client,
creates a transaction to handle it (a local variable), and
creates the object using new, without even storing the pointer
returned by new. The object, of course, registers itself as
a listener to whatever the client is interrested in, and
registers itself under its identifier (sent by the client), so
that the client can find it and remove it later, when the client
is no longer interested in the events. There are a number of
pointers to the object, but no one "owns" it (unless, perhaps,
you want to consider code on the client machine). And it can be
deleted in a number of situations: it might, for example,
register itself as an observer on the connection, so it can self
destruct if the connection is lost. Note that it is the logic
of the EventForwardingDiscriminator that knows that loss of
connection should cause end of life. This information is
encapsulated in the EventForwardingDiscriminator, so other parts
of the software don't have to know about it. It will auto
destruct as well if the client sends it a request for it to do
so. But again, only it knows if and when such requests are
valid: in the case of an EventForwardingDiscriminator, they're
usually universally valid, but in the case of a CrossConnection
(a type which manages external session switched connections),
the object will simply start refusing to accept new sessions,
but will not auto destruct until the last session closes.
Again: it is the object itself which contains this logic; the
rest of the program does not have to know that
EventForwardingDiscriminator can be destructed immediately, but
CrossConnection can't.
--
James