bmk
bmk

Reputation: 1568

What good is the c++'s `const` promise?

I am trying to understand c++'s const semantics more in depth but I can't fully understand what really the constness guarantee worth is. As I see it, the constness guarantees that there will be no mutation, but consider the following (contrived) example:

#include <iostream>
#include <optional>
#include <memory>

class A {
public:
  int i{0};

  void foo() {
    i = 42;
  };
};

class B {
public:
  A *a1;
  A a2;

  B() {
    a1 = &a2;
  }

  void bar() const {
    a1->foo();
  }
};

int main() {
  B b;
  std::cout << b.a2.i << std::endl; // output is 0

  b.bar();

  std::cout << b.a2.i << std::endl; // output is 42
}

Since bar is const, one would assume that it wouldn't mutate the object b. But after its invocation b is mutated. If I write the method foo like this

void bar() const {
    a2.foo();
}

then the compiler catches it as expected. So it seems that one can fairly easily circumvent the compiler with pointers. I guess my main question is, how or if I can be 100% sure that const methods won't cause any mutation to the objects they are invoked with? Or do I have completely false expectations about const?

Why does c++ allow invocation of non-const methods over pointers in const methods?

EDIT:

thanks to Galik's comment, I now found this:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4372.html

Well, this was exactly what I was looking for! Thanks! And I find Yakk's answer also very helpful, so I'll accept his answer.

Upvotes: 3

Views: 496

Answers (5)

R Sahu
R Sahu

Reputation: 206667

So it seems that one can fairly easily circumvent the compiler with pointers.

That is indeed true.

I guess my main question is, how or if I can be 100% sure that const methods won't cause any mutation to the objects they are invoked with?

The language guarantees that only in a local sense.

Your class is, indirectly, the same as the following:

struct Foo
{
  int* ptr;
  Foo() : ptr(new int(0)) {};

  void bar() const { *ptr = 10; }
};

When you use:

Foo f;
f.bar();

the member variable of f did not change since the pointer still points to the location after the call to f.bar() as it did before the call. In that sense, f did not change. But if you extend the "state" of f to include the value of what f.ptr points to, then the state of f did change. The language does not guarantee against such changes.

It's our job, as designers and developers, to document the "const" semantics of the types we create and provide functions that preserve those semantics.

Or do I have completely false expectations about const?

Perhaps.

Upvotes: 1

Remy Lebeau
Remy Lebeau

Reputation: 597081

When a class method is declared as const, that means the implicit this pointer inside the method is pointing at a const object and thus the method cannot alter any of the members of that object (unless they are explicitly declared as mutable, which is not the case in this example).

The B constructor is not declared as const, so this is of type B*, ie a pointer to a non-const B object. So a1 and a2 are non-const, and a1 is declared as a pointer to a non-const A object, so the compiler allows a1 to be pointed at a2.

bar() is declared as const, so this is of type const B*, ie a pointer to a const B object.

When bar() calls a1->foo(), a1 is const by virtue of this pointing at a const B object, so bar() can't change a1 to point at something else. But the A object that a1 is pointing at is still deemed to be non-const by virtue of a1's declaration, and foo() is not declared as const, so the compiler allows the call. However, the compiler can't validate that a1 is actually pointing at a2, a member of B that is supposed to be const inside of bar(), so the code breaks the const contract and has undefined behavior.

When bar() tries to call a2.foo() instead, a2 is const by virtue of this pointing at a const B object, but foo() is not declared as const, so the compiler fails the call.

const is just a safety catch for well-behaving code. It does not stop you from shooting yourself in the foot by using misbehaving code.

Upvotes: 0

SergeyA
SergeyA

Reputation: 62603

This is a correct observation. In const-qualified functions (bar in your example) all data members of the class are behaving as if they are const data members when accessed from this function. With pointers, it means that the pointer itself is constant, but not the object it points to. As a matter of fact, your example can be very much simplified into:

int k = 56;
int* const i = &k;
*i = 42;

There is a big difference between pointer to constant object and constant pointer, and one needs to understand it, so that 'promises', which were not given in the first place, would not seem to be broken.

Upvotes: -1

n. m. could be an AI
n. m. could be an AI

Reputation: 120021

Constness by itself doesn't guarantee you anything. It only takes away rights of specific code to mutate an object through a specific reference. It doesn't take away rights of other code to mutate the same object through other references, right under your feet.

Upvotes: 2

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275740

const tells the caller "this shouldn't mutate the object".

const helps the implementor with some errors where accidentally mutating state generates errors unless the implementor casts it away.

const data (not references, actual data) provides guarantees to the compiler that anyone who modifies this data is doing undefined behaviour; thus, the compiler is free to assume that the data is never modified.

const in the std library makes certain guarantees about thread safety.

All of these are uses of const.


If an object isn't const, anyone is free to const_cast away const on a reference to the object and modify it.

If an object is const, compilers will not reliably diagnose you casting away const, and generating undefined behavior.

If you mark data as mutable, even if it is also marked as const it won't be.

The guarantees that the std provides based off const are limited by the types you in turn pass into std following those guarantees.


const doesn't enforce much on the programmer. It simply tries to help.

No language can make a hostlie programmer friendly; const in C++ doesn't try. Instead, it tries to make it easier to write const-correct code than to write const-incorrect code.

Upvotes: 6

Related Questions