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

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.

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.

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.

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.

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.

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.

Related

Methods for getting a const view of a container of smart pointers [duplicate]

Consider the following code snippet:
class A
{
public:
void nonConstFun()
{
}
};
class B
{
private:
A a_;
A * pA_;
public:
void fun() const
{
pA_->nonConstFun();
//a_.nonConstFun(); // Gives const related error
}
};
int main()
{
B b;
b.fun();
}
Here I am expecting the compiler to fail the compilation for lack of constness for calling A::nonConstFun() inside B::fun() irrespective of the type of A object.
However the compiler complains for the object, but not for the pointer. Why?
I am using VS2017 on Windows 10.
The other answers explain the T* const vs T const * which is what is happening. But it is important to understand the implication of this beyond just the mere syntax.
When you have a T* inside a structure the pointer is inside the object (part of the layout of the objet), but the pointed object is physically outside of the structure. That is why a const object with a T* member is not allowed to modify the pointer, but it is allowed to modify the pointed object - because physically the pointed object is outside the enclosing object.
And it is up to the programmer to decide if the pointed object is logically part of the enclosing object (and as such should share constness with the enclosing) or if it is logically an external entity. Examples of former include std::vector, std::string. Examples of the latter include std::span, std::unique_ptr, std::shared_ptr. As you can see both designs are usefull.
The shortcoming of C++ is that it doesn't offer an easy way to express a logical constness as stated above (what you actually expected from your code).
This is known and for this exact purpose there is an experimental class which is not yet standard propagate_const
std::experimental::propagate_const is a const-propagating wrapper for
pointers and pointer-like objects. It treats the wrapped pointer as a
pointer to const when accessed through a const access path, hence the
name.
struct B
{
A a_;
std::experimental::propagate_const<A *> pA_;
void fun()
{
pA_->nonConstFun(); // OK
}
void fun() const
{
// pA_->nonConstFun(); // compilation error
}
};
It is enforced.
If you try changing the pointer, the compiler will not let you.
The thing that the pointer points to, however, is a different conversation.
Remember, T* const and T const* are not the same thing!
You can protect that by either actually making it A const*, or simply by writing your function in the manner that is appropriate.

const_cast 'this' in const method to assign 'this' to outer variable?

Have a look at the following code:
struct Foo;
Foo* bar;
struct Foo {
void func() const {
bar = this;
}
}
int main() {
Foo().func();
}
This does not work as bar won't accept a const Foo*. To get around this, const_cast could be used:
struct Foo {
void func() const {
bar = const_cast<Foo*>(this);
}
}
Is this safe? I'm very cautious when it comes to using const_cast, but in this case it seems legit to me.
No, this is potentially dangerous.
func() is marked const which means that it can be called by a const object:
const Foo foo;
foo.func();
Because this is const Foo*.
If you const_cast away the const you end up with a Foo* to a const object. This means that any modification to that object through the non-const pointer (or through any copy of that pointer, bar in this case) will get you undefined behavior since you are not allowed to modify a const object (duh).
Plus the obvious problem is that you're lying to the consumer of class Foo by saying func() won't modify anything while you're doing the opposite.
const_cast is almost never correct and this seems like an XY-problem to me.
If by "safe" you mean "not undefined behavior" then yes, it is safe. The value of bar will point to the created object as you'd expect.
However, const_cast is generally not recommended because it breaks the convention that a const thing will not be changed, and can easily produce undefined behavior (see this comment below). In this case you can simply do:
struct Foo {
void func() const {
bar = const_cast<Foo*>(this);
bar->modify();
}
void modify() { ... }
}
And you will be modifying the object in a const method, which is unexpected in general and undefined behavior if the instance of Foo on which the method is called was initially declared as const. However, it is up to you to get the logic right. Just note that everyone else will expect const things to be const, including the standard library, and usually (if not always) a better code design is possible.
Under some circumstances it is safe, namely when you have a non-const Foo object. If you have a const Foo object however, you're not allowed to modify it, and the compiler will not catch the bug because of the cast. So it is not a good idea to use this.
Note that in your example Foo is a temporary which gets destroyed on the next line of main().

Why do const references extend the lifetime of rvalues?

Why did the C++ committee decide that const references should extend the lifetime of temporaries?
This fact has already been discussed extensively online, including here on stackoverflow. The definitive resource explaining that this is the case is probably this GoTW:
GotW #88: A Candidate For the “Most Important const”
What was the rationale for this language feature? Is it known?
(The alternative would be that the lifetime of temporaries is not extended by any references.)
My own pet theory for the rationale is that this behavior allows objects to hide implementation details. With this rule, a member function can switch between returning a value or a const reference to an already internally existent value without any change to client code. For example, a matrix class might be able to return row vectors and column vectors. To minimize copies, either one or the other could be returned as a reference depending on the implementation (row major vs column major). Whichever one cannot be returned by reference must be returned by making a copy and returning that value (if the returned vectors are contiguous). The library writer might want leeway to change the implementation in the future (row major vs column major) and prevent clients from writing code that strongly depends on if the implementation is row major or column major. By asking clients to accept return values as const ref, the matrix class can return either const refs or values without any change to client code. Regardless, if the original rationale is known, I would like to know it.
It was proposed in 1993. Its purpose was to eliminate the inconsistent handling of temporaries when bound to references.
Back then, there was no such thing as RVO (return value optimization), so simply banning the binding of a temporary to a reference would have been a performance hit.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/1993/N0345.pdf
You are not questioning why const references are allowed to bind to temporaries, but merely why they extend the lifetime of those temporaries.
Consider this code:
struct A
{
void foo() const;
};
A bar();
const A& a = bar();
a.foo(); // (1)
If the lifetime of the temporary returned by bar() were not extended, then any usage of a (exemplified by the line (1)) would lead to undefined behavior. This would make binding non-parameter const references to temporaries completely useless.
EDIT (addressing OP's comment):
Thus the real question should be why a const reference variable (that is not a function parameter) is allowed to bind to a temporary. I don't know the original justification for it (Richard Hodges' answer may be the only true one), but it provides us with one useful feature. Considering the following example:
struct B
{
virtual void foo() const;
};
B bar();
const B& b = bar();
b.foo(); // (1)
The only difference of this example from the previous one is that B::foo() is virtual. Now, what if we decide to introduce a new class D as a subclass of B and change the return type of bar() from B to D?
struct B
{
virtual void foo() const;
};
struct D : B
{
virtual void foo() const;
};
//B bar();
D bar();
const B& b = bar();
b.foo(); // This will call D::foo()
// In the end the temporary bound by b will be correctly destroyed
// using the destructor of D.
Thus, binding const references to temporaries simplifies taking advantage of dynamic polymorphism for objects that are returned by value.

Does this code subvert the C++ type system?

I understand that having a const method in C++ means that an object is read-only through that method, but that it may still change otherwise.
However, this code apparently changes an object through a const reference (i.e. through a const method).
Is this code legal in C++?
If so: Is it breaking the const-ness of the type system? Why/why not?
If not: Why not?
Note 1: I have edited the example a bit, so answers might be referring to older examples.
Edit 2: Apparently you don't even need C++11, so I removed that dependency.
#include <iostream>
using namespace std;
struct DoBadThings { int *p; void oops() const { ++*p; } };
struct BreakConst
{
int n;
DoBadThings bad;
BreakConst() { n = 0; bad.p = &n; }
void oops() const { bad.oops(); } // can't change itself... or can it?
};
int main()
{
const BreakConst bc;
cout << bc.n << endl; // 0
bc.oops(); // O:)
cout << bc.n << endl; // 1
return 0;
}
Update:
I have migrated the lambda to the constructor's initialization list, since doing so allows me to subsequently say const BreakConst bc;, which -- because bc itself is now const (instead of merely the pointer) -- would seem to imply (by Stroustrup) that modifying bc in any way after construction should result in undefined behavior, even though the constructor and the caller would have no way of knowing this without seeing each others' definitions.
The oops() method isn't allowed to change the constness of the object. Furthermore it doesn't do it. Its your anonymous function that does it. This anonymous function isn't in the context of the object, but in the context of the main() method which is allowed to modify the object.
Your anonymous function doesn't change the this pointer of oops() (which is defined as const and therefore can't be changed) and also in no way derives some non-const variable from this this-pointer. Itself doesn't have any this-pointer. It just ignores the this-pointer and changes the bc variable of the main context (which is kind of passed as parameter to your closure). This variable is not const and therefore can be changed. You could also pass any anonymous function changing a completely unrelated object. This function doesn't know, that its changing the object that stores it.
If you would declare it as
const BreakConst bc = ...
then the main function also would handle it as const object and couldn't change it.
Edit:
In other words: The const attribute is bound to the concrete l-value (reference) accessing the object. It's not bound to the object itself.
You code is correct, because you don't use the const reference to modify the object. The lambda function uses completely different reference, which just happen to be pointing to the same object.
In the general, such cases does not subvert the type system, because the type system in C++ does not formally guarantee, that you can't modify the const object or the const reference. However modification of the const object is the undefined behaviour.
From [7.1.6.1] The cv-qualifiers:
A pointer or reference to a cv-qualified type need not actually point
or refer to a cv-qualified object, but it is treated as if it does; a
const-qualified access path cannot be used to modify an object even if
the object referenced is a non-const object and can be modified through
some other access path.
Except that any class member declared mutable (7.1.1) can be modified,
any attempt to modify a const object during its lifetime (3.8) results
in undefined behavior.
I already saw something similar. Basically you invoke a cost function that invoke something else that modifies the object without knowing it.
Consider this as well:
#include <iostream>
using namespace std;
class B;
class A
{
friend class B;
B* pb;
int val;
public:
A(B& b);
void callinc() const;
friend ostream& operator<<(ostream& s, const A& a)
{ return s << "A value is " << a.val; }
};
class B
{
friend class A;
A* pa;
public:
void incval() const { ++pa->val; }
};
inline A::A(B& b) :pb(&b), val() { pb->pa = this; }
inline void A::callinc() const { pb->incval(); }
int main()
{
B b;
const A a(b); // EDIT: WAS `A a(b)`
cout << a << endl;
a.callinc();
cout << a << endl;
}
This is not C++11, but does the same:
The point is that const is not transitive.
callinc() doesn't change itself a and incval doesn't change b.
Note that in main you can even declare const A a(b); instead of A a(b); and everything compile the same.
This works from decades, and in your sample you're just doing the same: simply you replaced class B with a lambda.
EDIT
Changed the main() to reflect the comment.
The issue is one of logical const versus bitwise const. The compiler
doesn't know anything about the logical meaning of your program, and
only enforces bitwise const. It's up to you to implement logical const.
This means that in cases like you show, if the pointed to memory is
logically part of the object, you should refrain from modifying it in a
const function, even if the compiler will let you (since it isn't part
of the bitwise image of the object). This may also mean that if part of
the bitwise image of the object isn't part of the logical value of the
object (e.g. an embedded reference count, or cached values), you make it
mutable, or even cast away const, in cases where you modify it without
modifying the logical value of the object.
The const feature merely helps against accidental misuse. It is not designed to prevent dedicated software hacking. It is the same as private and protected membership, someone could always take the address of the object and increment along the memory to access class internals, there is no way to stop it.
So, yes you can get around const. If nothing else you can simply change the object at the memory level but this does not mean const is broken.

is this destructor valid in c++?

String::~String() {
std::cout<<"String()" <<std::endl;
}
I wonder if this implementation of destructor is valid?
And another question about const member function qualifier, I know the const function can not change the variables in this class, it just read-only. If there is no other weird questions about it, I think I can understand it, but I saw some questions as following:
It allows the invocation of a non-const member function for the object pointed to by this
It guarantees that only mutable member variables of the object pointed to by this can be changed
It ensures that all constants remain invariable
It prevents inheritance
It allows changes to the state of the object pointed to by this
Based on my understanding, it is very hard to check which one is right, so I guess all of them are wrong?
The destructor is technically just another function, there doesn't seem anything wrong syntactically with this destructor to me, so it seems valid
That is all there is to const member functions, you cannot modify the data. These functions are automatically invoked by a const instance of the class. So if you have two functions with the same signature except for const-ness, it will chose the const version for const instances, and for non-const instances, it will depend on how you use it that determines which version is invoked
a) you cannot invoke non-const member functions within a const member function
b)Correct
c) correct
d) i'm unsure what you mean by preventing inheritance. IF you declare a function as virtual, const or not, it is inherited and can be overridden by subclasses
e) in const member functions, all data is considered const, unless declared a mutable.
Yes, that is a valid destructor.
const does not prevent inheritance. Nor does it bring about invariant behavior in the class's methods.
This question is actually multiple questions, though.
I recommend reading C++ FAQS.
I wonder if this implementation of destructor is valid?
There is nothing wrong with the destructor. But the question is: is that all you want to do in the destructor? Destructor is usually used to free the resources object holds when its alive; so it should free them when its going to die, so that others can use them. If it doesn't free them, then those resources will not be used by others as long as the program runs. Such a situation is usually referred to as Resource Leak, and if the resource is memory, its called Memory Leak.
Regarding question 2.
A nice way to think of member function cv qualifiers is to think of them as qualifiers on the 'this' pointer.
For example, if we wrote some C++ code in C:
class A
{
public:
void f1 ();
void f2 () const;
private:
int i;
};
This is the same as the following C code:
struct A
{
int i;
};
void f1 (A * const this); // Non const member
void f2 (A const * const this); // Const member
Another important thing to understand is that when you refer to a non static data member of a class, (*this). is implicitly added to it:
void A::f1 ()
{
i = 0; // This and the next line have the same meaning
(*this).i = 0;
}
When the member function is const, the this pointer is declared as pointing to a const object:
void A::f2 () const
{
(*this).i = 0; // Error '*this' is const, so cannot modify (*this).i
}
One of the conclusions from this, and something that people sometimes find is surprising, is that a const member function can still modify data pointed to by a member pointer. For example:
class A
{
public:
void f () const
{
*i = 0;
}
private:
int * i;
};
This looks wrong, but it's actually fine. (*this).i is const and so you would not be able to change what i points to, but i is still a pointer to a non const int and so we can modify the value pointed to by i.