Re: Using a macro to simplify implementing non-const methods in terms of const overload
Chris Morley wrote:
class foo
{
public:
std::string& bar() {return bar_;}
const std::string& bar() const
{
return const_cast<foo*>(this)->bar();
}
private:
std::string bar_;
};
^ This _is_ what I intended.
But that's really a very bad idea because
(*) it's easier to accidentally violate const correctness
(*) if you accidentally modify something in the non-const version
of bar() you will invoke undefined behaviour in cases like
const foo f; // f is really const!
f.bar(); // Ooops!
I'm not sure it is all that bad because you'd only want such a
construct in a situation where you are doing something trivial like
returning a reference/pointer to a structure/class.
No matter how trivial the code, applying a const_cast<Foo*> to an object
defined as 'const Foo bar;' already lands you in the land of UB.
After that point, whatever the compiler does to your code, it is correct
by definition.
Perhaps you have a
usage counter but otherwise the non-const version would be as trivial
as "return x;". More complexity with different requirements for const
& non-const function version should hint that they need different
function names.
Here is a slightly less trivial but still similar complete program.
#include "stdio.h"
#include <string>
#include <vector>
struct Thing {
Thing() : k(0), s("init") {}
int k;
std::string s;
};
class foo {
public:
foo() : _Count(0) {_V.resize(3);}
Thing& bar(int n) {++_Count; return _V[n];}
const Thing& bar(int n) const {return
const_cast<foo*>(this)->bar(n);}
void Display() const
{
printf("count=%d\n",_Count);
for (std::vector<Thing>::size_type i=0;i<_V.size();i++) printf("%d:
k=%d,
s=%s\n",i,_V[i].k,_V[i].s.c_str());
}
private:
std::vector<Thing> _V;
int _Count;
};
int main(int argc, char* argv[])
{
const foo t;
The compiler is fully within its right to mark the memory page that
contains the object t as being read-only. If it chooses to do so, any
usage of the function foo:bar() will most likely result in a crash.
And even if it does not choose to make t physically read-only, the
compiler can do whatever it wants when you cast away the constness.
t.Display();
const Thing& thing1=t.bar(1);
t.Display();
// lets force a cast just because we can
Thing& thing2 = const_cast<foo*>(&t)->bar(2);
thing2.s="oops?";
t.Display();
}
I don't see the problem or the UB - seriously please tell me if there
is a problem here. It behaves as intended with VS2008 & GCC even the
forced removal of the const.
That is the biggest problem with most instances of UB: The compilers do
not diagnose it and in small test programs they seem to give sensible
results.
Can you give me a realistic example of
the "oops" with casting through to the non-const bar()?
When used with a compiler that targets really memory-constrained
devices, it is not unreasonable to expect that t will be allocated in
ROM, which, depending on the hardware layout, may cause very bizarre
effects when calling foo::bar.
Regards,
Chris
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://c-faq.com/
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]