Convention for Object Lifetime Requirement

B

Belebele

Suppose that a class A depends on class B because an object of class B
is passed into A's constructor. See below:

class B { ... };

class A {
public:
A(B const& b);
...
};

Does anybody use any convention (comment, code, etc) to indicate
whether or not the object of class A requires b to exist beyond the
call to A's constructor?

For example, in the case of copy constructors, I would find odd to
learn that for objects o1 and o2 of certain class O, where o1 was
constructed from o2, o1 requires o2 to exist beyond the call to o1's
constructor.

I would like to extend the discussion to other types of dependencies,
for example, when A declares a member function that takes an object of
class B as a parameter.

Thanks:

Belebele
 
R

Roland Pibinger

Suppose that a class A depends on class B because an object of class B
is passed into A's constructor. See below:

class B { ... };

class A {
public:
A(B const& b);
...
};

Does anybody use any convention (comment, code, etc) to indicate
whether or not the object of class A requires b to exist beyond the
call to A's constructor?

No. This setting seems to be an anti-idiom. When A depends on B then
let A create B in the constructor:

class A {
B b;
public:
A (int i) : b(i) {}
...
};
For example, in the case of copy constructors, I would find odd to
learn that for objects o1 and o2 of certain class O, where o1 was
constructed from o2, o1 requires o2 to exist beyond the call to o1's
constructor.

In that case you can only prevent copy construction and assignment.
I would like to extend the discussion to other types of dependencies,
for example, when A declares a member function that takes an object of
class B as a parameter.

References to 'outside' objects also violates encapsulation. If A
really needs B then pass a B object as argument to the function, eg.

class A {
// as above
void foo (const B& b, float f) { ... }
...
};

Best wishes,
Roland Pibinger
 
M

mlimber

Belebele said:
Suppose that a class A depends on class B because an object of class B
is passed into A's constructor. See below:

class B { ... };

class A {
public:
A(B const& b);
...
};

Does anybody use any convention (comment, code, etc) to indicate
whether or not the object of class A requires b to exist beyond the
call to A's constructor?

Yes, use smart pointers instead of raw pointers or references when
there is some special relationship. For instance, if A takes over
ownership of a B object, pass a std::auto_ptr<B> into A's constructor,
or if A doesn't uniquely own that B object but needs it to exist after
the constructor has completed, accept a std::tr1::shared_ptr<B> (or
boost::shared_ptr said:
For example, in the case of copy constructors, I would find odd to
learn that for objects o1 and o2 of certain class O, where o1 was
constructed from o2, o1 requires o2 to exist beyond the call to o1's
constructor.

I would like to extend the discussion to other types of dependencies,
for example, when A declares a member function that takes an object of
class B as a parameter.

The same idioms apply in these two cases, methinks.

Cheers! --M
 
P

Phlip

Roland said:
No. This setting seems to be an anti-idiom. When A depends on B then
let A create B in the constructor:

class A {
B b;
public:
A (int i) : b(i) {}
...
};

Google for "construction encapsulation".

(And it's heartwarming to see how, each year, this newsgroup gets posts
detracting from the good memes of yesteryear as if they were "myths", or
"anti-idioms"...)
 
A

Axter

mlimber said:
Yes, use smart pointers instead of raw pointers or references when
there is some special relationship. For instance, if A takes over
ownership of a B object, pass a std::auto_ptr<B> into A's constructor,
or if A doesn't uniquely own that B object but needs it to exist after
the constructor has completed, accept a std::tr1::shared_ptr<B> (or


The same idioms apply in these two cases, methinks.

If you use a reference counting smart pointer, be careful not to add
cycles to your code, because the cyclic dependency prevents refcounts
from ever going to
zero, and therefore leaves memory leaks in your code.

Also see alternative to boost::shared_ptr
http://axter.com/smartptr

----------------------------------------------------------------------------------------
David Maisonave
http://axter.com

Top ten member of C++ Expert Exchange:
http://www.experts-exchange.com/Cplusplus
----------------------------------------------------------------------------------------
 
D

Daniel T.

"Belebele said:
Suppose that a class A depends on class B because an object of class B
is passed into A's constructor. See below:

class B { ... };

class A {
public:
A(B const& b);
...
};

Does anybody use any convention (comment, code, etc) to indicate
whether or not the object of class A requires b to exist beyond the
call to A's constructor?

A couple people have said "use a smart pointer" one even specified
shared_ptr. I think this is a bad idea in general. For one thing, there
is no guarantee that the B object passed in was created on the heap, it
might be stack based for all A knows. For another, if B is sending
itself to A, (and is also wrapped in a shared_ptr by some other means,
it can't possibly be good for it to send 'this' to A's constructor.

For example, in the case of copy constructors, I would find odd to
learn that for objects o1 and o2 of certain class O, where o1 was
constructed from o2, o1 requires o2 to exist beyond the call to o1's
constructor.

I would like to extend the discussion to other types of dependencies,
for example, when A declares a member function that takes an object of
class B as a parameter.

The general rule I use is that if the parameter is passed by reference
then the parameter is not going to be stored. The caller is free to
destroy the parameter object at anytime after the function returns. If
the parameter is passed in by pointer then it may be stored, check the
documentation to see how long (either until the end of the function,
until certain non-const member-functions are called, or until object
destruction.)
 
M

mlimber

Daniel said:
A couple people have said "use a smart pointer" one even specified
shared_ptr. I think this is a bad idea in general. For one thing, there
is no guarantee that the B object passed in was created on the heap, it
might be stack based for all A knows.

You are right that you couldn't pass an automatic object -- that's part
of the trade-off for using this idiom. The implicit requirement of
using a smart pointer as a constructor parameter is that the B object
be created in such as way as to be compatible with using that smart
pointer, and since the deleter of some (e.g., boost::shared_ptr) can be
specified, the object could be created by various methods. Also, in the
case that B is non-copyable but A needs to retain an instance of it, a
(smart) pointer of some kind is the only option.
For another, if B is sending
itself to A, (and is also wrapped in a shared_ptr by some other means,
it can't possibly be good for it to send 'this' to A's constructor.

This is true, but there are ways around it, not least of which is
considering different designs. What we're concerned with here is making
sure that an A's stored reference to a B object doesn't dangle or leak.
Passing a raw pointer/reference gives the most flexibility but
consequently (1) communicates the least about A's intentions toward
that B object and (2) allows a greater number of errors. Using the
smart pointer idiom clarifies A's intentions by encoding and enforcing
the life time expectations for the B object by making explicit who is
responsibile for destroying B and when it should be done.
The general rule I use is that if the parameter is passed by reference
then the parameter is not going to be stored. The caller is free to
destroy the parameter object at anytime after the function returns. If
the parameter is passed in by pointer then it may be stored, check the
documentation to see how long (either until the end of the function,
until certain non-const member-functions are called, or until object
destruction.)

This is a commendable convention, however, who is to say that the user
of your code (or the programmer who maintains it in several years) will
follow it? Using the smart pointer idiom enforces the life time
management requirements that are required by A in a way that your
convention and personal discipline cannot.

Cheers! --M
 
D

Daniel T.

"mlimber said:
You are right that you couldn't pass an automatic object -- that's part
of the trade-off for using this idiom. The implicit requirement of
using a smart pointer as a constructor parameter is that the B object
be created in such as way as to be compatible with using that smart
pointer, and since the deleter of some (e.g., boost::shared_ptr) can be
specified, the object could be created by various methods. Also, in the
case that B is non-copyable but A needs to retain an instance of it, a
(smart) pointer of some kind is the only option.


This is true, but there are ways around it, not least of which is
considering different designs. What we're concerned with here is making
sure that an A's stored reference to a B object doesn't dangle or leak.
Passing a raw pointer/reference gives the most flexibility but
consequently (1) communicates the least about A's intentions toward
that B object and (2) allows a greater number of errors. Using the
smart pointer idiom clarifies A's intentions by encoding and enforcing
the life time expectations for the B object by making explicit who is
responsibile for destroying B and when it should be done.


This is a commendable convention, however, who is to say that the user
of your code (or the programmer who maintains it in several years) will
follow it? Using the smart pointer idiom enforces the life time
management requirements that are required by A in a way that your
convention and personal discipline cannot.

My convention isn't the only one that requires discipline. Use of a
smart pointer also requires discipline. When an object is placed in a
smart pointer, one must ensure that (a) the object was dynamically
allocated, (b) that the smart pointer knows how to properly deallocate
it and (c) no other pointer to that object exists either as a raw
pointer, or within another smart pointer type.

So given, for example, two libraries that I want to use which use two
different smart pointers, what benefit do they give me when I want to
share an object between them? If anything my plight is worse than if
they (or at least one of them) used raw pointers.

Please understand I think smart pointers are a great idea, they are used
to excellent effect in some languages where every object is held by one.
But unless and until one smart pointer class holds every dynamically
allocated object no matter what library creates it, and cannot be
accidentally made to hold objects that were not dynamically allocated,
use of the smart pointer requires no less discipline, nor is it any less
error prone than use of a raw pointer.
 
M

mlimber

Daniel said:
My convention isn't the only one that requires discipline. Use of a
smart pointer also requires discipline. When an object is placed in a
smart pointer, one must ensure that (a) the object was dynamically
allocated, (b) that the smart pointer knows how to properly deallocate
it and (c) no other pointer to that object exists either as a raw
pointer, or within another smart pointer type.

Of course you are right that different discipline is also required for
proper smart pointer usage. My point (perhaps poorly communicated) was
primarily that *if* the smart pointer is used properly (just as it is
expected that one will not pass a dangling reference/pointer into
A::A() in your convention), then passing one to or returning one from a
function clearly communicates and enforces expectations for the
life-time management of that object. Use of smart pointers are
certainly not fool-proof, but using them across the board generally
makes things simpler and safer (especially when exceptions come into
play).

Compared to this explicitness, using raw pointers/references
necessitates nothing about life-time management since one can use them
"properly" in a number of ways -- your excellent convention being just
one of those. Smart pointers have (relatively) clearly defined rules
and expectations; raw pointers do not. Your convention may exist in
your personal or your company's coding standards, the written
documentation, or code comments, but that makes it more dependent on
the people involved and their discipline. Making life-time management
instead depend on the language rules via RAII and smart pointers makes
code more robust.
So given, for example, two libraries that I want to use which use two
different smart pointers, what benefit do they give me when I want to
share an object between them? If anything my plight is worse than if
they (or at least one of them) used raw pointers.

Well, if one uses std::auto_ptr and the other uses
std::tr1::shared_ptr, obviously they are incompatible because of
differing requirements, and such a case would be incompatible in your
convention as well, though I would hasten to point out that, unlike
with smart pointers, this incompatibility would not be clear just from
the interface.

If one uses std::tr1::shared_ptr and the other a roughly equivalent
reference-counted smart pointer (e.g., Loki::SmartPtr), then you do
have a problem, though you might be able to resolve it by supplying
compatible policies (for Loki::SmartPtr) or with a partial
specialization of the smart pointer class (for some other
implementation). ISTM that that problem will be mitigated somewhat
if/when shared_ptr becomes an official part of the standard. Then you
could justly complain to the library vendor that they should use
standard features rather than non-standard but equivalent ones.

In my experience, most third-party libraries today don't offer smart
pointers in their interfaces mainly because of the non-standard nature
of the beasts. Hopefully, that will change for the better if/when the
standardization includes more smart pointers, but for the time being,
one can use smart pointers internally in the project and convert them
to raw pointers when sending to third-party libraries (cf. using
std::vector with C APIs).
Please understand I think smart pointers are a great idea, they are used
to excellent effect in some languages where every object is held by one.
But unless and until one smart pointer class holds every dynamically
allocated object no matter what library creates it, and cannot be
accidentally made to hold objects that were not dynamically allocated,
use of the smart pointer requires no less discipline, nor is it any less
error prone than use of a raw pointer.

I agree fully that the discipline required for using smart pointers is
different, but I would argue that their use is indeed less error prone,
especially in the face of exceptions. The same thing is true of using
std::vector instead of C-style arrays: though the former requires
different discipline for proper usage (e.g., making sure iterators are
not invalidated), its benefits -- clearly defined life-time management,
exception-safety, and elimination of common programmer errors (e.g.,
delete instead of delete[]) -- out-weigh the downsides in most
circumstances (cf. FAQ 34.1).

Cheers! --M
 
B

Belebele

mlimber said:
You are right that you couldn't pass an automatic object -- that's part
of the trade-off for using this idiom. The implicit requirement of
using a smart pointer as a constructor parameter is that the B object
be created in such as way as to be compatible with using that smart
pointer, and since the deleter of some (e.g., boost::shared_ptr) can be
specified, the object could be created by various methods.

I did not know such feature (the deleter policy for the smart pointer)
existed in some standard way (that is, on a library somewhere). It
occurs to me that it can be put to good use in case the B object is not
in a smart pointer form. If the client of A knows that the B object is
guaranteed to meet the lifetime requirements imposed by A, it can
create a smart pointer to B with a deleter that does nothing and pass
it to A.
This is a commendable convention, however, who is to say that the user
of your code (or the programmer who maintains it in several years) will
follow it?

Enough said. However, if we do not know better (or are not fully
comfortable with the alternatives), the comment convention is, well,
highly commendable. (I will stick to using references as parameters if
clients are required to provide the B object, and pointers if the B
object may or may not be provided)
 
R

Roland Pibinger

No. This setting seems to be an anti-idiom. When A depends on B then
let A create B in the constructor:

Actually, there is an idiom where the above code makes sense:
dependency inversion, e.g.

Connection conn (....);
Database db (conn);
Table mytable (db);
MyObject* pObj = mytable.readByPrimaryKey (123);

To prevent errors (or make them less probable) all objects in the
'dependeny inversion chain' should be non-copyable/non-assignable.

Best wishes,
Roland Pibinger
 
M

mlimber

mlimber said:
You are right that you couldn't pass an automatic object -- that's part
of the trade-off for using this idiom. The implicit requirement of
using a smart pointer as a constructor parameter is that the B object
be created in such as way as to be compatible with using that smart
pointer, and since the deleter of some (e.g., boost::shared_ptr) can be
specified, the object could be created by various methods. Also, in the
case that B is non-copyable but A needs to retain an instance of it, a
(smart) pointer of some kind is the only option.


This is true, but there are ways around it, not least of which is
considering different designs. What we're concerned with here is making
sure that an A's stored reference to a B object doesn't dangle or leak.
Passing a raw pointer/reference gives the most flexibility but
consequently (1) communicates the least about A's intentions toward
that B object and (2) allows a greater number of errors. Using the
smart pointer idiom clarifies A's intentions by encoding and enforcing
the life time expectations for the B object by making explicit who is
responsibile for destroying B and when it should be done.

I retract both of these concessions to Daniel T.

First, using a custom deleter (in particular, a deleter that does
nothing), one certainly can send an object allocated on the stack to a
function expecting a smart pointer such as std::tr1::shared_ptr. (One
might worry that the function might try to retain a copy of the pointer
to the object, which will dangle when the automatic object goes out of
scope. This is true, but it equally applies to raw pointers.)

Second, std::tr1::shared_ptr certainly can point to "this" by
inheriting from std::tr1::enable_shared_from_this. (Alternately, one
can use a boost::intrusive_ptr, though this is not part of TR1 and is
not preferred except for legacy code.)

For more details, see the online docs or chapter 1 of Bjorn Karlsson's
_Beyond the C++ Standard Library: An Introduction to Boost_.

Cheers! --M
 

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

Forum statistics

Threads
473,763
Messages
2,569,562
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top