Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Constness when used on classes introduces a way to create objects that behave differently in different contexts. Are you calling a member function from a const or from a non-const pointer? Depending on that you could be running different code.

In my opinion you should make your mind about what the class does: is it mutable? then don't use it with const. Unfortunately, you are still required to use const in many places: copy constructors and operators, for example. However, you can still choose to minimize the use of const, and avoid it whenever possible.



That is an argument to avoid const overloading, not constness generally.

I would say that constness is a massive boon so I use it on the vast majority of declarations. It reduces cognitive load by being able to assume that variables are not going to change.


I agree that const is overall a good thing; however, it is tricky even in what you might think are simple cases. Take this for example:

    include <iostream>
    
    void foo(const int& a, int& b)
    {
	    std::cout << "a: " << a << std::endl;
	    std::cout << "b: " << b << std::endl;
    
	    ++b;
    
	    std::cout << "a: " << a << std::endl;
	    std::cout << "b: " << b << std::endl;
    }
    
    int main() {
	    int a = 1;
	    foo(a, a);
    }
Here the value of a changes in the middle of the function even though it's a const reference, because of aliasing. Here const doesn't mean the value won't change--only that you can't change it through that particular reference. Yes, const is useful, but if you don't know what it actually does it will bite you in the ass, just like most of C++.


This does not seem like a strong argument. Yes, table salt is useful, but if you don't know what it actually does and you put it in your eyes, it will burn them, just like most, well, everything...

A const reference guarantees to the caller that the callee won't modify the object, not to the callee that the object won't be modified.


Except that it doesn't. Thanks to const_cast, you can take away constness from pointers, so where does your "guarantee" comes from?


The guarantee is not against a malicious agent, but a reasonable programmer who may make mistakes.


A function may not reliably cast a pointer-to-const-T to a pointer-to-T because T may actually be immutable, in which case modifying it would be UB. Like all casts, const_cast exists as an escape hatch in the type system for when you know that what you are doing is safe, even if the compiler can't prove it.


You misunderstand reference-to-const, it does not imply that what "a" points to is immutable, it is a contract to the caller that foo will not use "a" in any non-const way.


That's exactly what I said; however, when you introduce people to C++ that seems rather counter-intuitive and can lead to a lot of mistakes. Yes, you can tell people "Well, you don't understand what the standard says!" but there's still something to be said for language features that don't take several pages to explain what they really do.


I just gave an explanation of reference-to-const in 1 sentence, not several pages. Also, my objection to your comment was your focus on what const means to foo() when the value is in what it means to main().


Isn't this inferable from the code itself, though? I was expecting some subtle gotcha, so I had to read your comment three times to realise there was none!

It's to be expect that we can change a-through-&b, but not a-through-&a in foo.


I think the objection is that b may get passed throughout the program such that it's no longer local to a. Then you have 'const Foo& a' that you expect will never change, but your function reading from that calls something that mutates a 'Foo* b' that aliases the object (probably through a member variable), and suddenly you have some very hard to track down bugs. If you're multi-threaded, you have a data race.

You either need to be very strict about creating non-const references to objects, or you need to enforce immutability at the class level. Unfortunately the latter has all sorts of other practical problems, often including efficiency trade-offs or awkward APIs.


> you have 'const Foo& a' that you expect will never change, but your function reading from that calls something that mutates a 'Foo* b' that aliases the object

And this is exactly why const means pretty close to nothing. Compilers can't enforce it in so many situations that it is almost like the "auto" keyword in pre-C++11 times.


It still documents intended behavior, though. C++ is basically a giant clusterfuck if you want the compiler to actually enforce anything - because of pointer arithmetic, there's no guarantee not only that const Foo& a is immutable, but even that it points to a valid Foo or that accessing it won't crash your computer. It's trivially easy to trigger undefined behavior that may do anything up to pwning your computer, whether you make your classes immutable or not. The point of const, access control modifiers, references, static typing, RAII, and all of the other restrictions on C++ programming is to put up giant signposts that say "Don't do that! Here be dragons!" rather than actually prevent you from doing it.

If you want the compiler to actually enforce safety properties of the language, use a language like Haskell or Rust.


This seems to me a lot of trouble for "documented" behavior. It is much better to design your classes in a reasonable way and stop worrying about const issues. If you rely on const to write good code, I guarantee you that sooner or later you will be in a lot of trouble.


Const-ness gives you a way to express a very common pattern in software, where an object is constructed piecemeal in one section of the code and then "frozen" and only const references get passed around from then on. If you were to express this at the class level, you'd need awkward circumlocutions like the Builder pattern, comments on methods, or friend member functions.


The builder pattern is the right way to go. It is stronger than const-references, because once the object is created, there is no way to unfreeze it. It guarantees that the object is truly const, no matter what. On the other hand, const keyword in C++ does not guarantee the object is really const. Your reference may be const, but something else might still have a non-const reference and mutate the object.


Most of the time `const` is only needed for the caller to ensure that the object won't get changed when passed to another function.

In addition, to enforce immutability in C++, you have to disable copy/move constructors and assignment operators, which removes much benefit of using values instead of references.


> `const` is only needed for the caller to ensure that the object won't get changed when passed to another function.

But const cannot guarantee this, because it can be cast away so easily. This could be true just for your own code, but then it is you who is responsible for maintaining the immutability, not the compiler.


> because it can be cast away so easily

By this logic, none of the static type checks in C++ is of any use, since they can be cast away easily as well.


Types exist to help you organize your code. Const (for classes) just promotes the misguided idea that you should design objects with mutable and immutable parts at the same time. The main objection to this is that your objects should be either value objects (therefore immutable) or reference objects. There is no need for const if you design your classes in this way. On the other hand, the const idea requires you to spread "const" everywhere you might want immutability, otherwise the compiler will fight you at each line of your code. Either way, it is a loss-loss proposition.


I see this sometimes in code at work. As soon as you need to stuff one of the 'const' objects into any kind of container you start requiring unique_ptrs everywhere, and then you start thinking how much easier your life would be if you wrote Haskell for a living instead.


It that is your intent, then build an immutable class with some friends to initialize the object if necessary. The const thing is so overloaded in C++ that is means close to nothing in terms of intent, and it can be easy circumvented.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: