Why isn't the const qualifier working on pointer members on const objects?
Asked Answered
H

3

7

I know this has been asked a lot, but the only answers I could find was when the const-ness was actually casted away using (int*) or similar. Why isn't the const qualifier working on pointer type member variables on const objects when no cast is involved?

#include <iostream>

class bar {
public:
    void doit()       { std::cout << "    bar::doit() non-const\n"; }
    void doit() const { std::cout << "    bar::doit() const\n"; }
};

class foo {
    bar* mybar1;
    bar mybar2;
public:
    foo() : mybar1(new bar) {}
    void doit() const {
        std::cout << "foo::doit() const\n";
        std::cout << "  calling mybar1->doit()\n";
        mybar1->doit();  // This calls bar::doit() instead of bar::doit() const
        std::cout << "  calling mybar2.doit()\n";
        mybar2.doit(); // This calls bar::doit() const correctly
    }
    // ... (proper copying elided for brevity)
};

int main(void)
{
    const foo foobar;  // NOTE: foobar is const
    foobar.doit();
}

The code above yields the following output (tested in gcc 4.5.2 and vc100):

foo::doit() const
  calling mybar1->doit()
    bar::doit() non-const         <-- Why ?
  calling mybar2.doit()
    bar::doit() const
Heavy answered 6/5, 2011 at 22:28 Comment(14)
As an aside, examples are clearer when you don't name variables the same as your class type, for "foo". :)Eleanore
What do you mean by your "why"? You made foo object const, but the object pointed by foo.mybar is not const. You never made it const. So, why do you expect the const method to be called for *foo.mybar? Basically, when you are illustrating an obvious and straightforward behavior and ask a "why" question there's no way to even begin answering this question, since there's no way to even begin to understand how and why someone would ask it.Schmitt
It is as if you asked why 1 is equal to 1. How does one answer such a question? There's just no way to answer it because there's really no question here.Schmitt
@AndreyT: I never declared foo.mybar2 const either, so I think the question is perfectly valid.Heavy
@Oskar N.: mybar2 is a part of foo. When you make foo const, you automatically make mybar2 const. *mybar1 is not a part of foo, so it doesn't become const.Schmitt
@AndreyT: While what you say is right, it does warrant an explanation, so the question is a good one and well justified.Corporate
@sbi: I'm not saying that it doesn't. I'm just saying the there might be a multitude of different ways that led to this question. And in order to address it properly it could be useful to know what exactly is the source of the OPs confusion. A mere "why" is not very helpful. The OP should have explained why he thought it should call the constant version. Then one would be able to pinpoint the error in the OP's logic.Schmitt
@AndreyT: I can't see how to interpret "What do you mean by your 'why'?" other than that you consider the question moot.Corporate
My eyes got tired reading the example code. Try calling the classes A and B, producing minimal output, and being consistent in the output - in the current version sometimes you indent a call, sometimes you prefix it with "calling". And for simple examples, always use "using namespace std;". This will make life a lot easier for those of us trying to work out what your example code is about.Salman
@unapaersson: We're trying to turn this into an FAQ entry. Why don't you go ahead and make your changes? Except for the using namespace std, that's an abomination and I'm against using it in even the simplest sample code, because novices are learning it from there.Corporate
@unapersson: I find the opposite with a std using directive. If they type std:: I know exactly what they're talking about and it's less likely to be a problem in how they reduced the code to a test case (which are often not independently run/verified afterwards).Eleanore
@AndreyT: I knew this, but got bitten when trying to implement the visitor pattern traversing a const object. I wanted my visit(const A&) to be called instead of visit(A&) and couldn't understand why not the first was being called on my const object. I missed that I was actually calling the function through a pointer and hence the confusion. With all the talk about const-correctness in C++ I thought for a second that const would actually call the const-version, throwing all my understanding about pointers out the window for a short while.Heavy
@Corporate @fred I won't change the question because I believe in respecting the original intent of the questioner. The ability to edit questions and answers on SO is one that must be used with discretion. I believe that examples should in general not use the std:: prefix, and if you edit my posts here to add it you had better believe there will be ructions! And I use "using namespace std" all the time (but not everywhere) in my production code.Salman
You know, @unapersson, your opinion in the using debate would not have been half as convincing had you not given such a good counterargument to mine.Corporate
E
15

When a foo instance is const, its data members are const too, but this applies differently for pointers than you might at first think:

struct A {
  int *p;
};

A const obj;

The type of obj.p is int * const, not int const *; that is, a constant pointer to int, not a pointer to constant int.

For another way to look at it, let's start with a function:

template<class T>
T const& const_(T const &x) {
  return x;
}

Now imagine we have an A instance, and we make it const. You can imagine that as applying const_ on each data member.

A nc;
// nc.p has type int*.
typedef int *T;  // T is the type of nc.p.

T const &p_when_nc_is_const = const_(nc.p);
// "T const" is "int * const".

const T &be_wary_of_where_you_place_const = const_(nc.p);
// "const T" is "int * const".
// "const T" is *not* "const int *".

The variable be_wary_of_where_you_place_const shows that "adding const" is not the same as prepending "const" to the literal text of a type.

Eleanore answered 6/5, 2011 at 22:30 Comment(1)
Couldn't have said it better.Embay
H
1

I am going to answer my own question in this case. Fred Nurk's answer is correct but does not really explain the "why". mybar1 and *mybar1 are different. The first refer to the actual pointer and the latter the object. The pointer is const (as mandated by the const-ness on foo; you can't do mybar1 = 0), but not the pointed to object, as that would require me to declare it const bar* mybar1. The declaration bar* mybar1 is equivalent to bar* const mybar1 when the foo object is const (i.e. pointer is const, not pointed to object).

Heavy answered 6/5, 2011 at 23:13 Comment(0)
P
0

C++ by default gives a so called bitwise constness - meaning it ensures that no single bit of object has been changed, so it just checks the address of the pointer.

You can read more about it in great book "Effective c++" by S. Meyers

Poleyn answered 30/4, 2014 at 16:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.