/*
Occasionally somebody will suggest that statically typed language X (Java, C#, Scala, F#, whatever) would be better off with C++'s "const", a type qualifer that supposedly makes objects immutable in certain contexts[1]. I'm not convinced. Const requires a fair amount of work and provides fairly small guarantees. This post is a bit of literate C++ program demonstrating some of the weaknesses in const.
There are a couple of holes that I want to dismiss quickly. First there's the weakness that const can be cast away. But hey, the C and C++ motto is "casting away safety since 1972." Blowing your leg off with a cast is half the fun of using the C derived languages. Second, there's the useful, if slightly misnamed, weakness of the "mutable"[2] keyword which can be applied to a field and says "this field can be mutated even in const contexts." Mutable is useful for things like memoizing.
But even ignoring those very deliberate issues, there are still a couple of fundamental weaknesses in how C++ deals with const.
For my example I'm going to use a simple toy class, a Counter that can be incremented (mutating its state) and then queried to see what the current count is.
*/
#include <iostream> using namespace std; class Counter { int _count; public: Counter() : _count(0) {} void inc() { _count++; }
/*
A perfectly good use of const. The count() query doesn't change the state of this Counter.
*/
int count() const{ return _count; } };
/*
Indirection
So far, so good. But we can easily destroy const with a bit of indirection. Imagine I need something that holds a pair of Counters and, for whatever reason, I need to do so via pointers.
*/
class CounterPair { Counter *_c1; Counter *_c2; public: CounterPair() { _c1 = new Counter(); try { _c2 = new Counter(); } catch(...) { delete _c1; throw; } } // technically should have copy constructor // and operator=, but I'm too lazy ~CounterPair() { delete _c1; delete _c2; }
/*
These two methods query the state of the CounterPair and are safely const
*/
int count1() const { return _c1 -> count(); } int count2() const { return _c2 -> count(); }
/*
Up to this point everything is golden. But these next two methods modify the state of the CounterPair, yet are marked const.
*/
void inc1() const { _c1 -> inc(); } void inc2() const { _c2 -> inc(); } };
/*
What gives? Well, C++ doesn't appear to understand the that state and memory are two different things. I didn't modify the memory that lies directly in one CounterPair envelope but I'm clearly modifying the state of the CounterPair. "Transitive constness" is one area where the D programming language does much better than C++.
Aliasing
An even more subtle issue happens with aliasing. The following function does not modify its first argument but does modify its second. The first is marked const.
*/
void incSecondCounter(Counter const &c1, Counter &c2) { c2.inc(); } int main(int argc, char** argv) { Counter c;
/*
But if we create a bit of aliasing then c1's referent gets modified
*/
Counter const &c1 = c; Counter &c2 = c; cout << c1.count() << endl; incSecondCounter(c1, c2); cout << c1.count() << endl; }
/*
In this toy program that looks silly, but in larger programs aliasing can be quite subtle. And aliasing is a tricky, tricky problem to deal with well at the type level so even the D language will have an equivalent hole in const. At best what const says here is that "the object won't be modified via that particular reference," which is a heck of a lot weaker than "the object won't be modified."
Conclusion
Const correctness requires a lot of typing of both the push-the-buttons-on-the-keyboard kind and the keep-certain-static-properties-consistent variety. Yet it gives back relatively little. Even without poking holes in const using casting or "mutable" it's easy to get mutation in something that's supposed to be immutable. C++'s variety of const doesn't deal with indirection at all well and aliasing is a tricky nut that would substantially alter a language's type system. Is const worth adding to other languages? I'm not sure.
Footnotes
- C'99 also has a const keyword with very similar semantics. So if you replace references with pointers and strip out the thin veneer of OO this article would cover C as well.
- Misnamed because fields are generally mutable even without the keyword. But I suspect the committee would balk at "mutable_even_under_const."
*/