Raw pointers not evil after all?

C

Christopher Pisz

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
2) It is an an interface rather than concrete
3) Storing, passing, and manipulating pointers is more optimal than
copying a large object by value and references weren't an option.
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. 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.
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?
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.

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.

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.

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

James Kanze

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

James Kanze

One might implement a graph editor in C++, where there is a
person (the user of the software) who can add or remove
other persons he likes. He also might add like-relations
beetween arbitrary two persons (»A likes B«, »B likes A«, »B
likes C«), but when there is no »likes-path« anymore from
the user to a persons, the person can be deleted from the graph.
Due to cycles, reference counting is difficult here, so one
effectively needs to write a subroutine to find whether
after a modification to the graph a person is still reachable
from the user via a path of likes, i.e., a garbage collector.

Interesting example. Yes, data graphs like this are one case
where garbage colection is a definite win. If the "persons" had
significant behavior, and in particular, they had to do
something when they were removed from the graph, you'd still
have to solve the problem independently of garbage collection.
Luckily, such cases are rare. (At least, I've never seen one.)
 
Ö

Öö Tiib

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.

He very likely meant "it is polymorphic". Or at least that was
how I understood it. On all cases when we need polymorphic
components of objects we have to use pointers. I feel that
unique_ptr is lot better than raw pointer for polymorphic
data members.
 
G

guy.tristram

I might add that if you need weak_ptr, you're using shared_ptr
in a context where it isn't appropriate.

Does this imply there are no appropriate uses for weak_ptr? I have to say
that whenever I've started to design something using weak_ptr, I've changed my
mind.

Guy
 
8

88888 Dihedral

(e-mail address removed)æ–¼ 2013å¹´5月4日星期六UTC+8上åˆ1時34分11秒寫é“:
Does this imply there are no appropriate uses for weak_ptr? I have to say

that whenever I've started to design something using weak_ptr, I've changed my

mind.



Guy

The check before used principle in C was deleted in C++.
 
G

Gerhard Fiedler

mike3 said:
So what kind of factors should one consider when making the decision
to
use whichever kind of pointer? What should one weigh?

Also, what do you think about this with smart pointers?:

// f should not own whatever smth points to
void f(T *smth) // f() takes a simple pointer
{
...
}

void g() // or could be a member function of some class
{
smart_ptr<T> smartPointer(...) // owns an object of type T
// could instead be a class member

...

f(smartPointer.get()); // doesn't transfer ownership to f

...
}

Is this OK? Or does it violate the intended usage of the smart
pointer? Like if smart_ptr is an auto_ptr or C++11 "unique_ptr" or
something else that works like those. Isn't it supposed to maintain a
_unique_ reference to the object it points to? So then you make and
pass a raw pointer... now there's two pointers to that object! Yet we
don't want f to own the object.

It depends on your own coding rules. For example, if you have a rule
that you never, ever take ownership of a raw pointer outside of the
scope where it was created or store it beyond the original scope, your f
is quite ok. Your coding rule guarantees that smth won't be stored
beyond the scope of f, and then the .get() call is just fine...
smartPointer guarantees that smth stays valid for its whole lifetime.

Gerhard
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,102
Messages
2,570,646
Members
47,247
Latest member
GabrieleL2

Latest Threads

Top