9

I'm not sure to entirely understand the philosophy behind the const keyword used on class methods.

I've always thought that using the const keyword in the signature of a class method meant that this method would not modify the fields of the object it's called on. However when we use for example a vector, there is an overload for operator [] that is not const :

whateverT & vector<whateverT>::operator [] ( size_t pos )

No matter what you do with the reference you are given, it will perform no modifications on the vector's fields, even if the referenced item is modified.

Another example :

template<class T> class Array
{
    T * _items;

    T & operator [] ( size_t pos ) const
    {
        return _items[ pos ];
    }
}

I can use the const keyword here because the value of _items is not modified (no matter what we do with what it points to). For the compiler this is correct, but if i access one of the items and modify it, the operator [] will allow the modification of the array even though it's supposed to be const, i.e supposed not to modify the " contents " of the array.

What should be done in that kind of case ? Use or don't use the const keyword ?

Thank you :)

Virus721
  • 8,061
  • 12
  • 67
  • 123
  • 2
    The compiler insist only on non altered members. However, if you have a pointer, the pointer itself might not be altered, but its content. This breaks const correctness logically, you are responsible to design it properly (maybe you want the break) –  Dec 17 '14 at 20:18

4 Answers4

14

It is a matter of your design decision. Constness is intended to be used as a design concept. It is supposed to help you to implement a higher-level concept of what you consider "modifying" and "non-modifying" access. By properly using const on your class methods you can make the property of being "non-modifiable" to propagate from one object to another through references.

"Referencing other objects" can represent at least two different design relationships:

  • It can be used to implement aggregation, in which case the referee is considered to be an integral part of the referrer. In that case you are typically supposed to be enforcing constness of the access to all parts of the complete object: if the referrer is constant, the referee should also be considered constant. It is your responsibility to enforce the latter by properly const-qualifying your class's interface.

    In order to support that concept you'd normally never attempt to modify the referee inside const methods of the referrer (even though it is formally possible). And the other way around: if some method of referrer modifies the content of referee, you should never declare that method const (even though it is formally possible). Also, const methods of referrer should never return non-constant references to the referee.

    In such cases the outside world is not even supposed to know that the aggregated object is stored by reference. It is a just an implementation detail. For the outside world everything should look as if the aggregated object is a direct immediate member of the referrer.

    That is exactly what you observe in case of std::vector. It is designed that way to make sure that the constness of the entire vector propagates to the constness of the vector elements. In order to achieve that it implements two versions of operator []

    reference       operator[]( size_type pos );
    const_reference operator[]( size_type pos ) const;
    
  • It can be used to implement pure referencing that does not imply aggregation. In that case the referee is considered a completely separate, unrelated object. The constness of the referrer is not supposed to propagate to the referee. It is OK in this case to modify the referee in the const methods of the referrer. It is OK to return non-constant references to the referee from const methods of the referrer.

    An example of this design would be standard smart pointers: a pointer refers to the pointee, but constness of the pointer does not in any way imply constness of the pointee. For example, std::shared_ptr has only one version of operator *

    T& operator*() const;
    

    which is declared const yet returns a non-constant reference to the pointed object.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • Good answer. I might be tempted to add something about "`const` means threat-safe to the `std`" – Yakk - Adam Nevraumont Dec 17 '14 at 20:27
  • @Yakk: `const` does not mean thread-safe to the `std`. It's undefined behavior to call a `const` member in one thread while another thread is modifying the container. – Mooing Duck Dec 17 '14 at 20:28
  • 1
    @mooingduck It is defined behavior for two `const` methods to be called by two different threads at the same time. Similarly, if you are in a `std` container, them calling a `const` qualified method from two different threads should be fair game. By saying `const` and putting yourself into a standard container, you make "certain guarantees" about being able to be called in more than one thread, and in exchange the `std` container makes certain thread safety guarantees about its own interfaces (the `const` ones and a few others). – Yakk - Adam Nevraumont Dec 17 '14 at 20:29
  • See this stack overflow question: http://stackoverflow.com/questions/14127379/does-const-mean-thread-safe-in-c11 -- the pithy "const means thread-safe" does need clarification. It doesn't mean **synchronized**, it means (in a sense) "I guarantee thread safety". A `const` object should be usable by multiple threads in all exposed interfaces. How this is implemented is up to the programmer of the object, not the language. (You can violate this rule, but be careful when talking to the `std` about such objects) – Yakk - Adam Nevraumont Dec 17 '14 at 20:32
1

I think it depends on what does the modification to the returned object means to your class, thinking about your program's logic and its objects model, not about pure syntax. Suppose you pass your Array to a function that receives a const T &, would you expect the function to modify what is inside the array? Is the array still the same if the funciton swaps two elements? I think in this case answer is no, and so you should return a const ref to the array's item in the const version of operator[].

Zaskar
  • 569
  • 3
  • 10
1

vector has two signatures for operator [] T& operator[] (size_type n) and const T& operator[] (size_type n). The first signature give you a reference to the data in the vector that you can modify. The second function gives you a constant reference that you can not modify. With const you can have it in a couple of places in a function

const T foo(int bar) // return a const T and take an int
T foo(const int bar) // return a T and take a const int
T foo(int bar) const // return a T and take an int but the function cannot change the state of the object

You can also mix and match these functions.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
0

Using const to define a const method means you want the method to always treat the 'this' pointer as a pointer to a constant object.

So, if I have interpreted your question correctly, then, because you are not touching the data members, whether or not implementing a const method doesn't really matter. But if you are paranoid OR care about maintenance and you want to ensure that the method should never modify the data members then you should make it a const method. Probably this is the philosophy you are looking for.

RcnRcf
  • 356
  • 1
  • 8