Look at this very simple piece of code:
struct A { char* s; };
class B
{
A* a;
public: B(const char* s) : a(new A()) {
int len = strlen(s);
a->s = new char[len + 1];
memcpy(a->s, s, len + 1);
}
~B() { delete [] a->s; delete a; }
const char* c_str() const { return a->s; }
const B& to_upper() const {
char* x = a->s;
int len = strlen(x);
for (int i = 0; i < len; i++)
{
char k = x[i];
if (k >= 'a' && k <= 'z')
x[i] -= 32;
}
a->say_hi();
return *this;
}
};
int main() {
B b = "hola mundo";
printf("%s\n", b.to_upper().c_str());
}
It works!! My question is… why?
The to_upper() method is const and modifies the value pointee by “a”. Ok, I am not able to do something like “a = nullptr;” because the compiler says: “You are trying to modify a read-only object”; but it lets me modifying the underlying values. Is this behavior correct? Shouldn’t the “a” type be converted to “const A*” in the same way that the type of “this” is converted to “const B*” in the const method?
Thanks!
The constness of a method translates to the constness of the
*thisobject, meaning that insideto_upperthethispointer has typeconst B *. That’s all there is to it. No more, no less. The effect of this is not different from what you would see in C language, for example. It makes pointerthis->aconst, but it doesn’t affect the pointee.In fact it is up to you to decide whether the constness of
Bpropagates to theAobject pointed bythis->a. The language gives you full freedom in making this decision. It is called “conceptual constness” (as opposed to “physical constness” or “logical constness”). The compiler observes and enforces only logical constness, while the purpose of keywordconstin OOP goes far beyond that: it allows you to implement the idea of conceptual constness in your design.If the
Aobject is considered an integral part ofB, then the constness ofBshould also mean constness ofA. But this is something you have to observe and enforce manually (or some smart pointer class can help you with this).If
Aobject is an independent object which just happens to be merely referenced fromB, then constness ofBshould not necessarily imply constness ofA.The compiler does not impose any decisions on you with respect to this, since compiler has no idea what object relationship you are trying to implement. In your design, the way I see it, the
Aobject is actually an integral part ofB, which means that you weren’t supposed to declare yourto_upperasconst. It is a modifying function. It changes what is perceived by the user as the value ofB. By declaringto_upperasconstyou are essentially “lying” to the user.