 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Raoul Gough Guest
|
Posted: Fri Aug 20, 2004 10:04 am Post subject: Returning reference to private data |
|
|
I remember learning years ago that returning a reference to private
data is a "bad thing" (probably read about it in one of Scott Meyer's
books). Now I'm starting to wonder again *why* it's a bad thing, and
if it may only be a bad thing because of the way C++ does its type
checking. Consider this:
class bounded_range {
int m_low;
int m_high;
public:
void include_in_range (int);
int high() const;
int low() const;
};
class price_size_range {
bounded_range m_price_range;
bounded_range m_size_range;
public:
bounded_range &price_range();
bounded_range &size_range();
};
I guess the conventional wisdom is that price_size_range should
instead have functions like include_in_price_range, high_price,
include_in_size_range, etc, which probably just forward the function
calls to the bounded_range members.
Well, this is kind of tedious, and if the implementation never
actually changes then the extra effort was unnecessary. This is the
"you aren't going to need it" argument, i.e. you may never need the
flexibility to change implementations, so don't waste effort providing
it.
If I was programming this in Python, I probably wouldn't hesitate to
use the quicker approach. If I want to change the implementation
later, I think I can whip up a small proxy class to return instead,
with the same interface as bounded_range, and any sensible clients
will not require any modification! [Note: Python is dynamically typed
- you don't say what type a variable should be, you just assign
something to it] In this case I only write the forwarding functions
(in the proxy class) when, and if, I need to change the
implementation. Neat!
So why doesn't this work in C++? Actually in some cases it does. If
client code restricted itself to things like the following, the same
technique would apply:
price_size_range x;
x.price_range().include_in_range (2);
x.price_range().high();
// etc.
Now price_range() can actually return anything, as long as that thing
provides the right interface. It seems to me that the only problems
arise when client code _names_ the type that is returned by the
function:
bounded_range &price_range = x.price_range();
price_range.include_in_range (4);
// etc.
I see two possible problems now. Firstly, this actually requires
price_size_range to have a member variable of type bounded_range.
Secondly, having bound the returned reference to a variable, the
client could destroy the price_size_range object and retain a dangling
reference.
On a slightly related note, I seem to remember seeing discussions
about adding a mechanism for automatic deduction of variable types,
e.g.
auto_type price_range = x.price_range();
price_range.include_in_range (4);
which would solve at least the first problem (and of course, garbage
collection solves the second problem :-)
Now what about if client code used a typedef defined in
price_size_range instead of naming the bounded_range type directly? In
some cases at least, a coding guideline could enforce the use of the
typedef, so would the whole problem with returning a reference to
private data then disappear?
--
Raoul Gough.
export LESS='-X'
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Francis Glassborow Guest
|
Posted: Fri Aug 20, 2004 2:40 pm Post subject: Re: Returning reference to private data |
|
|
In article <uvffewx6n.fsf (AT) yahoo (DOT) co.uk>, Raoul Gough
<RaoulGough (AT) yahoo (DOT) co.uk> writes
| Quote: | I remember learning years ago that returning a reference to private
data is a "bad thing" (probably read about it in one of Scott Meyer's
books). Now I'm starting to wonder again *why* it's a bad thing, and
if it may only be a bad thing because of the way C++ does its type
checking. Consider this:
class bounded_range {
int m_low;
int m_high;
public:
void include_in_range (int);
int high() const;
int low() const;
};
class price_size_range {
bounded_range m_price_range;
bounded_range m_size_range;
public:
bounded_range &price_range();
bounded_range &size_range();
};
I guess the conventional wisdom is that price_size_range should
instead have functions like include_in_price_range, high_price,
include_in_size_range, etc, which probably just forward the function
calls to the bounded_range members.
|
No, there are two issues:
1) return by plain reference allows uncontrolled modification of class
data. In general it is the task of member functions to maintain class
invariants and exposing data members this way invalidates controlled
access. This can be dealt with by return only const qualified
references.
2) Any return by reference exposes class internals and locks the design.
There are ways to avoid this as an issue (see c_str() in std::string)
but they have their own costs and basically offer extra opportunities
for users to write broken code.
--
Francis Glassborow ACCU
Author of 'You Can Do It!' see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
llewelly Guest
|
Posted: Sat Aug 21, 2004 3:24 am Post subject: Re: Returning reference to private data |
|
|
Raoul Gough <RaoulGough (AT) yahoo (DOT) co.uk> writes:
| Quote: | I remember learning years ago that returning a reference to private
data is a "bad thing" (probably read about it in one of Scott Meyer's
books). Now I'm starting to wonder again *why* it's a bad thing, and
if it may only be a bad thing because of the way C++ does its type
checking.
|
As soon as you provide referneces to private members, users can
assign pointers the addresses of those members, or bind
references to them. The pointers can then be passed to other
modules at will.
If you change the size, type, or interpretation of such members, you
will break user code.
Put that way - as you say, it *is* due in part to static type
checking; in a dynamically typed language, the size and the type
would not be constrained merely by providing a refernece.
However, even in a dynamically typed language, you still wouldn't be
able to change the interpretation of the member.
I think the only two attributes of such a member you can safely change
are its name, and the order it appears in in the class
declaration. (Or you could simply return a reference that points
elsewhere ... but that could make maintaining the same
interpretation interesting.)
| Quote: | Consider this:
class bounded_range {
int m_low;
int m_high;
public:
void include_in_range (int);
int high() const;
int low() const;
};
class price_size_range {
bounded_range m_price_range;
bounded_range m_size_range;
public:
bounded_range &price_range();
bounded_range &size_range();
};
I guess the conventional wisdom is that price_size_range should
instead have functions like include_in_price_range, high_price,
include_in_size_range, etc, which probably just forward the function
calls to the bounded_range members.
Well, this is kind of tedious, and if the implementation never
actually changes then the extra effort was unnecessary. This is the
"you aren't going to need it" argument, i.e. you may never need the
flexibility to change implementations, so don't waste effort providing
it.
|
If you are really sure the implementation will never change - then it
is even easier to simply make the members public. And that's not
necessarily bad; you'll seldom find a significant application
where public data members are never used, though local coding
conventions may require the warm fuzzy feeling obtained by using
'struct' and not 'class' to declare the containing types.
| Quote: | If I was programming this in Python, I probably wouldn't hesitate to
use the quicker approach. If I want to change the implementation
later, I think I can whip up a small proxy class to return instead,
with the same interface as bounded_range, and any sensible clients
will not require any modification! [Note: Python is dynamically typed
- you don't say what type a variable should be, you just assign
something to it] In this case I only write the forwarding functions
(in the proxy class) when, and if, I need to change the
implementation. Neat!
So why doesn't this work in C++?
|
C++ does not support re-creating the interface of a reference.
There's no language support for creating proxy references. You can't
overload operator. , and there is probably way too much code which
needs & to return the real address, and not some proxy pointer
type. If C++ did provide support for proxy references, you would
still need to provide a typedef, and require that your users
never explicitly name the type aliased by the typedef, you'd
still need to provide whatever rat's nest of implicit conversions
the original type had, and even then, you would still be unable
to provide 100% source compatibility; any code which treated the
type of the member as POD, and probably code which relied on
sizeof, would break. Binary compatibility, which private members
and public access member functions provide, is hopeless.
On the other hand, if you want dynamic typing, C++ does provide it,
with certain limitations; if you write:
struct abc
{
/* ... */
virtual ~abc();
};
struct bounded_range: public abc
{
/* ... */
};
struct price_size_range
{
typedef bounded_range range;
bounded_range &price_range();
bounded_range &size_range();
private:
/* ... */
};
you can later change the types of the private members to anything
derived from abc. It's not as flexibile as what python provides,
but sometimes it's what you need.
| Quote: | Actually in some cases it does. If
client code restricted itself to things like the following, the same
technique would apply:
price_size_range x;
x.price_range().include_in_range (2);
x.price_range().high();
// etc.
Now price_range() can actually return anything, as long as that thing
provides the right interface. It seems to me that the only problems
arise when client code _names_ the type that is returned by the
function:
bounded_range &price_range = x.price_range();
price_range.include_in_range (4);
// etc.
I see two possible problems now. Firstly, this actually requires
price_size_range to have a member variable of type bounded_range.
Secondly, having bound the returned reference to a variable, the
client could destroy the price_size_range object and retain a dangling
reference.
On a slightly related note, I seem to remember seeing discussions
about adding a mechanism for automatic deduction of variable types,
e.g.
auto_type price_range = x.price_range();
price_range.include_in_range (4);
which would solve at least the first problem (and of course, garbage
collection solves the second problem :-)
Now what about if client code used a typedef defined in
price_size_range instead of naming the bounded_range type directly? In
some cases at least, a coding guideline could enforce the use of the
typedef, so would the whole problem with returning a reference to
private data then disappear?
|
The typedef would conceal the *name* of the type. It can't conceal
the type's implicit conversions, the size of the type, its
PODness, whether or not it is builtin, or whether or not it is
polymorphic. Nor can it conceal the operations of the type. You
could document usage restrictions, and hope your users mostly
obey them. (for example, posix documents that pthread_t is not
guaranteed to be of arithmetic type - that restricts usage in a
good many ways. But since it isn't compiler-enforced, sometimes
those restrictions are followed, sometimes not - just the other
day I encountered: static_cast<unsigned long>(some_pthread_t)).
Note that I'm not claiming this idea is always bad; sometimes I think
this kind of compromise is exactly what is needed.
You could conceal more of the type's attributes by not providing the
definition of the member's type:
class bounded_range;
struct price_size_range
{
typedef bounded_range range;
bounded_range &price_range();
bounded_range &size_range();
private:
/* ... */
};
However this prevents use of the type's member functions, and
essentially requires it be reference type - a nice value type,
like bounded_range probably should be, doesn't allow this
trick. So for price_size_range this probably the wrong thing, but
other times it might be what you want.
C++ provides a bewildering array of ways to conceal different amounts
of information. private, protected, and public, aren't holy -
they are just familiar, and easy to understand.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
James Hopkin Guest
|
Posted: Sat Aug 21, 2004 3:30 am Post subject: Re: Returning reference to private data |
|
|
Raoul Gough <RaoulGough (AT) yahoo (DOT) co.uk> wrote
| Quote: | I remember learning years ago that returning a reference to private
data is a "bad thing" (probably read about it in one of Scott Meyer's
books). Now I'm starting to wonder again *why* it's a bad thing, and
if it may only be a bad thing because of the way C++ does its type
checking.
|
I believe the main reason for the guideline is a matter of
encapsulation.
An important responsibility of a class is to guarantee an invariant
between its (private) data members. If a class returns a non-const
reference to a private data member, it can no longer deliver that
guarantee.
If you are running up against this guideline, it might mean you are
thinking too low level. Make a distinction between simple collections
(usually implemented as aggregate structs in C++) and genuine classes
(which usually have all private data, and don't return 'handles' to
the data, as I believe Scott Meyers put it).
Cheers,
James
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Balog Pal Guest
|
Posted: Sun Aug 22, 2004 3:05 am Post subject: Re: Returning reference to private data |
|
|
"Raoul Gough" <RaoulGough (AT) yahoo (DOT) co.uk> wrote
| Quote: | I remember learning years ago that returning a reference to private
data is a "bad thing"
|
It is not a "bad thing", just defeats the whole idea of 'private'. You
create the situation equivalent having just a plain public data member.
Somewhat obfuscated too. :)
If you do not like public data, do not use it directly or indirectly. If you
like it, just use it. There's a plenty of materials around explaining how it
can be a nuisance. Those points may or may not apply to your design.
Paul
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Dave Harris Guest
|
Posted: Sun Aug 22, 2004 10:50 pm Post subject: Re: Returning reference to private data |
|
|
[email]RaoulGough (AT) yahoo (DOT) co.uk[/email] (Raoul Gough) wrote (abridged):
| Quote: | I remember learning years ago that returning a reference to private
data is a "bad thing" (probably read about it in one of Scott Meyer's
books). Now I'm starting to wonder again *why* it's a bad thing, and
if it may only be a bad thing because of the way C++ does its type
checking.
|
Others have now explained why: because it prevents the class from
maintaining its invariants.
| Quote: | I guess the conventional wisdom is that price_size_range should
instead have functions like include_in_price_range, high_price,
include_in_size_range, etc, which probably just forward the function
calls to the bounded_range members.
|
That's one approach. Another is to have methods which get and set the item
by copy:
class price_size_range {
bounded_range m_price_range;
bounded_range m_size_range;
public:
bounded_range price_range() const;
bounded_range size_range() const;
void set_price_range( bounded_range r );
void set_size_range( bounded_range r );
};
Or, more usually, use const references instead of copies for efficiency
(and take care around the lifetime issues).
Now you can use x.price_range.high() directly, and make changes via:
bounded_range r = x.price_range();
r.include_in_range( 10 );
x.set_price_range( r );
This avoids cluttering the price_size_range interface, while still
allowing that class to maintain its invariants.
-- Dave Harris, Nottingham, UK
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Raoul Gough Guest
|
Posted: Sun Aug 22, 2004 11:01 pm Post subject: Re: Returning reference to private data |
|
|
Francis Glassborow <francis (AT) robinton (DOT) demon.co.uk> writes:
| Quote: | In article <uvffewx6n.fsf (AT) yahoo (DOT) co.uk>, Raoul Gough
[email]RaoulGough (AT) yahoo (DOT) co.uk[/email]> writes
I remember learning years ago that returning a reference to private
data is a "bad thing" (probably read about it in one of Scott Meyer's
books). Now I'm starting to wonder again *why* it's a bad thing, and
if it may only be a bad thing because of the way C++ does its type
checking. Consider this:
class bounded_range {
int m_low;
int m_high;
public:
void include_in_range (int);
int high() const;
int low() const;
};
class price_size_range {
bounded_range m_price_range;
bounded_range m_size_range;
public:
bounded_range &price_range();
bounded_range &size_range();
};
I guess the conventional wisdom is that price_size_range should
instead have functions like include_in_price_range, high_price,
include_in_size_range, etc, which probably just forward the function
calls to the bounded_range members.
No, there are two issues:
1) return by plain reference allows uncontrolled modification of class
data. In general it is the task of member functions to maintain class
invariants and exposing data members this way invalidates controlled
access. This can be dealt with by return only const qualified
references.
|
I think I managed to forget about this issue by choosing a
particularly simple example, but you are right of course. Sometimes
returning a reference is already wrong in terms of functionality, not
just in terms of restricting flexibility for future change.
| Quote: |
2) Any return by reference exposes class internals and locks the design.
There are ways to avoid this as an issue (see c_str() in std::string)
but they have their own costs and basically offer extra opportunities
for users to write broken code.
|
Well OK, in the worst case it certainly does lock the design. The
thing that occured to me, and what I was trying to raise in my post,
is that only *certain* uses of the returned reference actually cause
problems. For example, using foo.price_range().low() does not
constrain the return type from price_range(), except that it must
provide a suitable member function called low(). In the face of
internal changes, it would be possible to provide a proxy object as
return type, sparing the initial effort required to hide the actual
internals until (and only if) it becomes necessary. I liked llewelly's
idea in his post of returning just an abstract interface, but that has
its own overheads of course.
--
Raoul Gough.
export LESS='-X'
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Raoul Gough Guest
|
Posted: Sun Aug 22, 2004 11:02 pm Post subject: Re: Returning reference to private data |
|
|
llewelly <llewelly.at (AT) xmission (DOT) dot.com> writes:
| Quote: | Raoul Gough <RaoulGough (AT) yahoo (DOT) co.uk> writes:
I remember learning years ago that returning a reference to private
data is a "bad thing" (probably read about it in one of Scott Meyer's
books). Now I'm starting to wonder again *why* it's a bad thing, and
if it may only be a bad thing because of the way C++ does its type
checking.
As soon as you provide referneces to private members, users can
assign pointers the addresses of those members, or bind
references to them. The pointers can then be passed to other
modules at will.
If you change the size, type, or interpretation of such members, you
will break user code.
Put that way - as you say, it *is* due in part to static type
checking; in a dynamically typed language, the size and the type
would not be constrained merely by providing a refernece.
However, even in a dynamically typed language, you still wouldn't be
able to change the interpretation of the member.
I think the only two attributes of such a member you can safely change
are its name, and the order it appears in in the class
declaration. (Or you could simply return a reference that points
elsewhere ... but that could make maintaining the same
interpretation interesting.)
|
I guess you're right - worst case, if client code relies on an actual
reference to member variable being returned. On the other hand, client
code that doesn't explicitly name the return type (e.g. using just a
typedef) allows a good deal more freedom that that.
| Quote: |
Consider this:
class bounded_range {
int m_low;
int m_high;
public:
void include_in_range (int);
int high() const;
int low() const;
};
class price_size_range {
bounded_range m_price_range;
bounded_range m_size_range;
public:
bounded_range &price_range();
bounded_range &size_range();
};
I guess the conventional wisdom is that price_size_range should
instead have functions like include_in_price_range, high_price,
include_in_size_range, etc, which probably just forward the function
calls to the bounded_range members.
Well, this is kind of tedious, and if the implementation never
actually changes then the extra effort was unnecessary. This is the
"you aren't going to need it" argument, i.e. you may never need the
flexibility to change implementations, so don't waste effort providing
it.
If you are really sure the implementation will never change - then it
is even easier to simply make the members public. And that's not
necessarily bad; you'll seldom find a significant application
where public data members are never used, though local coding
conventions may require the warm fuzzy feeling obtained by using
'struct' and not 'class' to declare the containing types.
|
The gospel seems to be that one should always assume that the
implementation *will* change, and invest the up-front effort to allow
this without forcing changes onto the clients. This seems a bit
unfortunate to me, and perhaps it is actually forced upon programmers
by the nature of the C++ language, rather than any more fundamental
issues of OO design.
| Quote: |
If I was programming this in Python, I probably wouldn't hesitate to
use the quicker approach. If I want to change the implementation
later, I think I can whip up a small proxy class to return instead,
with the same interface as bounded_range, and any sensible clients
will not require any modification! [Note: Python is dynamically typed
- you don't say what type a variable should be, you just assign
something to it] In this case I only write the forwarding functions
(in the proxy class) when, and if, I need to change the
implementation. Neat!
So why doesn't this work in C++?
C++ does not support re-creating the interface of a reference.
There's no language support for creating proxy references. You can't
overload operator. , and there is probably way too much code which
needs & to return the real address, and not some proxy pointer
type. If C++ did provide support for proxy references, you would
still need to provide a typedef, and require that your users
never explicitly name the type aliased by the typedef, you'd
still need to provide whatever rat's nest of implicit conversions
the original type had, and even then, you would still be unable
to provide 100% source compatibility; any code which treated the
type of the member as POD, and probably code which relied on
sizeof, would break. Binary compatibility, which private members
and public access member functions provide, is hopeless.
|
Youch! Any code that relies on sizeof() something defined in other
class deserves to break :-)
I wasn't suggesting the need for any kind of *generic* proxy
references (which, as you point out, are not possible in C++). On a
case by case basis, you can manually code a forwarding interface,
which passes each member function call through. This is more or less
the same effort as the conventional approach in the first place,
except that it only happens if necessary, instead of on the assumption
that the implementation will change at some point in the future.
| Quote: |
On the other hand, if you want dynamic typing, C++ does provide it,
with certain limitations; if you write:
struct abc
{
/* ... */
virtual ~abc();
};
struct bounded_range: public abc
{
/* ... */
};
struct price_size_range
{
typedef bounded_range range;
bounded_range &price_range();
bounded_range &size_range();
|
I think you meant:
abc &price_range()
so that the client doesn't know the real type?
| Quote: | private:
/* ... */
};
you can later change the types of the private members to anything
derived from abc. It's not as flexibile as what python provides,
but sometimes it's what you need.
|
Yes! This is exactly what I was trying to get at, without putting it
in the right terms. Unfortunately, this also means more up-front
costs, since you have first to define the interface, then the
implementation, all so that you have built in the flexibility that you
may (or may not) require in the future. However, this does parallel
the Python idea, that you should rely on the interfaces of objects you
deal with, and not necessarily their types.
That also incurs run-time costs, of course, especially if you want to
deal with the object lifetime issues. With particular usage patterns
(such as using a provided typedef, or compiler type deduction) these
run-time costs can be avoided, but rely on the clients playing ball.
Providing only an abstract interface restricts them to doing the right
thing. Pity there doesn't seem to be any way to get this effect
automatically. Imagine if there were such a thing as an "unnamable"
type, where the compiler is allowed to *deduce* the type, but client
code cannot refer to it by name (or take it's size :-)
| Quote: |
Actually in some cases it does. If
client code restricted itself to things like the following, the same
technique would apply:
price_size_range x;
x.price_range().include_in_range (2);
x.price_range().high();
// etc.
Now price_range() can actually return anything, as long as that thing
provides the right interface. It seems to me that the only problems
arise when client code _names_ the type that is returned by the
function:
bounded_range &price_range = x.price_range();
price_range.include_in_range (4);
// etc.
I see two possible problems now. Firstly, this actually requires
price_size_range to have a member variable of type bounded_range.
Secondly, having bound the returned reference to a variable, the
client could destroy the price_size_range object and retain a dangling
reference.
On a slightly related note, I seem to remember seeing discussions
about adding a mechanism for automatic deduction of variable types,
e.g.
auto_type price_range = x.price_range();
price_range.include_in_range (4);
which would solve at least the first problem (and of course, garbage
collection solves the second problem :-)
Now what about if client code used a typedef defined in
price_size_range instead of naming the bounded_range type directly? In
some cases at least, a coding guideline could enforce the use of the
typedef, so would the whole problem with returning a reference to
private data then disappear?
The typedef would conceal the *name* of the type. It can't conceal
the type's implicit conversions, the size of the type, its
PODness, whether or not it is builtin, or whether or not it is
polymorphic. Nor can it conceal the operations of the type. You
could document usage restrictions, and hope your users mostly
obey them. (for example, posix documents that pthread_t is not
guaranteed to be of arithmetic type - that restricts usage in a
good many ways. But since it isn't compiler-enforced, sometimes
those restrictions are followed, sometimes not - just the other
day I encountered: static_cast<unsigned long>(some_pthread_t)).
|
Those clients, you just can't trust anybody.
| Quote: |
Note that I'm not claiming this idea is always bad; sometimes I think
this kind of compromise is exactly what is needed.
You could conceal more of the type's attributes by not providing the
definition of the member's type:
class bounded_range;
struct price_size_range
{
typedef bounded_range range;
bounded_range &price_range();
bounded_range &size_range();
private:
/* ... */
};
However this prevents use of the type's member functions, and
essentially requires it be reference type - a nice value type,
like bounded_range probably should be, doesn't allow this
trick. So for price_size_range this probably the wrong thing, but
other times it might be what you want.
|
I don't think that provides any functionality for the client at all,
does it?
| Quote: |
C++ provides a bewildering array of ways to conceal different amounts
of information. private, protected, and public, aren't holy -
they are just familiar, and easy to understand.
|
It doesn't seem to provide a means of concealing the right amount of
information in this case. That's why so much code I see looks like this:
int foo::get_bar_data() {
// forward to m_bar
return m_bar.get_data();
}
--
Raoul Gough.
export LESS='-X'
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Glen Low Guest
|
Posted: Mon Aug 23, 2004 10:32 am Post subject: Re: Returning reference to private data |
|
|
| Quote: | I guess the conventional wisdom is that price_size_range should
instead have functions like include_in_price_range, high_price,
include_in_size_range, etc, which probably just forward the function
calls to the bounded_range members.
Well, this is kind of tedious, and if the implementation never
actually changes then the extra effort was unnecessary. This is the
"you aren't going to need it" argument, i.e. you may never need the
flexibility to change implementations, so don't waste effort providing
it.
|
There are a couple of techniques around it.
1. You could return a const reference instead of a reference, arguably
a little safer in that the client can't change the internals (and thus
the invariants) of the class.
2. You could typedef a "reference" type and work off assumptions that
proxies may be used instead -- no operator. etc. If you're energetic
enough, you can define a template proxy that just wraps an existing
data member by real reference, and use that until you need more
functionality.
I actually used this technique once when I had an MS ATL class that
had a bool data member that I needed to monitor or change the code
thereof; I changed that member to store a proxy instead and thankfully
the resulting code didn't break. I wouldn't recommend it though,
especially in such a retrofit fashion.
Cheers,
Glen Low, Pixelglow Software
www.pixelglow.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Raoul Gough Guest
|
Posted: Mon Aug 23, 2004 10:40 pm Post subject: Re: Returning reference to private data |
|
|
[email]brangdon (AT) cix (DOT) co.uk[/email] (Dave Harris) wrote in message news:<memo.20040822125704.1900C (AT) brangdon (DOT) m>...
| Quote: | RaoulGough (AT) yahoo (DOT) co.uk (Raoul Gough) wrote (abridged):
I remember learning years ago that returning a reference to private
data is a "bad thing" (probably read about it in one of Scott Meyer's
books). Now I'm starting to wonder again *why* it's a bad thing, and
if it may only be a bad thing because of the way C++ does its type
checking.
Others have now explained why: because it prevents the class from
maintaining its invariants.
I guess the conventional wisdom is that price_size_range should
instead have functions like include_in_price_range, high_price,
include_in_size_range, etc, which probably just forward the function
calls to the bounded_range members.
That's one approach. Another is to have methods which get and set the item
by copy:
class price_size_range {
bounded_range m_price_range;
bounded_range m_size_range;
public:
bounded_range price_range() const;
bounded_range size_range() const;
void set_price_range( bounded_range r );
void set_size_range( bounded_range r );
};
Or, more usually, use const references instead of copies for efficiency
(and take care around the lifetime issues).
Now you can use x.price_range.high() directly, and make changes via:
bounded_range r = x.price_range();
r.include_in_range( 10 );
x.set_price_range( r );
This avoids cluttering the price_size_range interface, while still
allowing that class to maintain its invariants.
|
Doesn't this also introduce the design lock-in though? Client code
that "knows" the return type is bounded_range const & (or even
bounded_range by value) will require changes if the design of
price_size_range changes. So I'm not sure that this addresses all of
the same issues as writing forwarding functions. Several people have
pointed out the class invariants problem, which const references can
certainly address, but I was actually more interested in the
interface-versus-named-type issue. I guess this only comes into
question if there are no higher-level invariants to maintain, which is
a special case.
As llewelly pointed out in another post, if the return type only
refers to an abstract base class, then the internal design is not
actually constrained at all. It should be possible to write a new
implementation of the ABC which handles whatever changes you would
otherwise have put in the forwarding functions. Unfortunately, the
amount of coding effort involved in setting up the ABC return type is
probably similar to writing the forwarding functions in the first
place. Then there are the run time costs and object lifetime issues.
Actually, static polymorphism probably provides a better solution,
which I guess is why I started wishing for "autotype" variables, or
some kind of unnamable types (i.e. client code could use the returned
object or reference only in resticted ways, that don't require naming
the actual type). Oh well, I guess it's just an idiom that could work
in Python but not really in C++.
--
Raoul Gough.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Dave Harris Guest
|
Posted: Wed Aug 25, 2004 10:09 pm Post subject: Re: Returning reference to private data |
|
|
[email]RaoulGough (AT) yahoo (DOT) co.uk[/email] (Raoul Gough) wrote (abridged):
| Quote: | Doesn't this also introduce the design lock-in though? Client code
that "knows" the return type is bounded_range const & (or even
bounded_range by value) will require changes if the design of
price_size_range changes.
|
There are various trade-offs and degrees of information hiding. Sometimes
it makes sense to, in effect, compose the interface of price_size_range
from "smaller" interfaces like bounded_range. This is a form of reuse with
the advantages which that implies. However, to reuse something is to
become dependant on it, which has disadvantages too. Each situation has to
be judged on its merits.
| Quote: | As llewelly pointed out in another post, if the return type only
refers to an abstract base class, then the internal design is not
actually constrained at all.
|
It's constrained to the Abstract Base Class. An ABC is hopefully more
stable than an implementation class, but you've still got the dependency.
Even in a dynamically checked language, you have a dependency. There is
something called the Law of Demeter which addresses this. It advocates
writing the forwarding functions (or some other solution), always, even in
Python. Code like:
int value = item.price_range().high();
specifies a path through the data structure. It is redundant; it
respecifies a connection which was already contained in the definition of
price_size_range. Every time you write such code, you hardwire the path a
little bit more, spreading the knowledge a little bit more widely. It
makes the app harder to restructure. It only makes sense if this path is
known to be very stable.
-- Dave Harris, Nottingham, UK
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|