Re: Using a macro to simplify implementing non-const methods in terms of const overload
On 25 Aug, 21:39, "Martin T." <0xCDCDC...@gmx.at> wrote:
Nick Hounsome wrote:
On 25 Aug, 12:41, "Martin T." <0xCDCDC...@gmx.at> wrote:
Hi all.
Implementing a non-const function in terms of it's const overload is an
accepted idea:
Occasionally (maybe in your real class?) but your examples and most
others that I have come across are actually just bad design.
Hmmm ... you mean to say that it is a sign of bad design if a member
function needs a cost and non-const version ??
I didn't say always - but usually.
The only time I can remember doing it myself (my memory isn't great)
is for tree navigation. (eg. node* parent()/const node* parent()
const)).
Note that the motivation here is not to have simultaneous const and
non-const access to a node but to maintain the logical constness of
the containing entity (stupid people will write just "node* parent()
const" and it will be fine with the compiler but logically wrong).
class foo {
public:
std::string & bar() {
This is a bad example because it exposes the implementation of foo
(foo MUST contain a std::string).
Even worse it allows a client to maintain a reference to the guts of a
foo after the object has been destroyed:
foo* fp = new foo;
std::string& s( fp->bar() );
delete fp;
s.length(); // ooops! Undefined behaviour.
To keep the implementation hidden you must instead have something like
"void setbar(const std::string&)" and your use of the const bar()
would nolonger be relevant. Contrariwise, if foo were really intended
to be just a glorified struct then bar_ should be public and you would
have no need for either method.
While I don't agree that the reference returning is such a bad thing,
you do raise a valid point.
Why not? You haven't addressed my reasons.
Do you think it is OK to constrain the internals of a class by the
interface used?
Do you not agree that my example of undefined behaviour is
undesirable?
The other advantage of setter methods over private variable access is
that you can track and/or validate changes.
If you return a reference to your bar_ and at some point you discover
that it is wrong then how do you track down where it changed? With
setbar(const std::string&) you can just set a breakpoint. With your
implementation it is much harder.
Or suppose that a new requirement is introduced that says that bar_
must always be upper case. With a setter you can either validate or
force it. Without a setter you have nothing.
Namely whether it's necessary to have such
accessors in the first place.
There are valid cases where we want to return a pointer/reference/handle
like thing from a mem.fun. and for these cases I think it makes sense to
provide a const + non-const version.
One example are the std library containers -- Consider this snippet from
std::vector from the STL supplied with Visual Studio 2008 Express:
// vector, line 758:
But this is completely different from your example.
A vector<T> MUST logically contains some T so returning a reference
doesn't expose anything that we could implement in any other way.
What we are hiding in vector is not the T itself but the details of
how more than one of them are held (plus giving some checking).
Your foo example holds a logical string which (in the absence of other
design info) need not necessarily be implemented as a std::string but
because bar() returns a reference it is impossible alter foo to use
char* instead (or CString if you're into Windows) without changing the
declaration of bar().
const_reference operator[](size_type _Pos) const
{ // subscript nonmutable sequence
#if _HAS_ITERATOR_DEBUGGING
if (size() <= _Pos)
{
_DEBUG_ERROR("vector subscript out of range");
_SCL_SECURE_OUT_OF_RANGE;
}
#endif /* _HAS_ITERATOR_DEBUGGING */
_SCL_SECURE_VALIDATE_RANGE(_Pos < size());
return (*(_Myfirst + _Pos));
}
reference operator[](size_type _Pos)
{ // subscript mutable sequence
#if _HAS_ITERATOR_DEBUGGING
if (size() <= _Pos)
{
_DEBUG_ERROR("vector subscript out of range");
_SCL_SECURE_OUT_OF_RANGE;
}
#endif /* _HAS_ITERATOR_DEBUGGING */
_SCL_SECURE_VALIDATE_RANGE(_Pos < size());
return (*(_Myfirst + _Pos));
}
I think it would kind of make sense to use the solution with the
const_cast instead of 1:1 duplicating 9 lines of code.
In this case you could.
If you did then in general you would have to implement the const
method in terms of the non-const method rather than the other way
around - This is because, for example, for general iterators rather
than just T*, it is not possible to convert from const_iterator to
iterator - You then run into the problem of potentially undefined
behaviour again.
Microsoft (or whoever their STL comes from) didn't, maybe they had a
reason. My initial posting was trying to find out. :-)
return const_cast< std::string& >( static_cast<foo
const*>(this)->bar() );
}
const std::string & bar() const {
return bar_;
}
void mooh() {
This method is NEVER necessary - Just delete it and nobody will ever
know the difference.
Agreed. I was just over eager to supply a completeish example.
cheers,
Martin
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]