Sign in to follow this  
oliii

mutable and const

Recommended Posts

I'm not a big fan of the 'mutable' storage class specifier. However, it seems inevitable, for example in a sort of stream base class.
Quote:
class Stream
{
    virtual bool read(void* ptr, const int len) const { m_pointer += len; return true; }
    virtual bool write(const void* ptr, const int len) { m_pointer += len; return true; }

    mutable int pointer; // needs to be mutable to push the stream pointer in the read() method.
};
Is there any clean way round it (while still maintaining the read() method to be declared 'const').

Share this post


Link to post
Share on other sites
I can only imagine 2 other ways, both of which seem more unclean than a mutable variable:

* have a pointer/reference type: but Stream then depends on "third party storage"
* use, ahem, const_cast<>, or better, don't.

Note that a function being const shall not change the classes externally visible state, not its internal one. I guess streams are pretty borderline in this respect, because two consecutive reads with the same arguments might not yield the same return value. Dunno, I would prefer mutable.

[Edited by - phresnel on June 2, 2009 9:00:40 AM]

Share this post


Link to post
Share on other sites
const-correctness is about the immutability of the object's state, not its individual members. An object is considered immutable even if some internally used attributes change but the object's state appears to be unchanging from an external point of view. This is perfectly fine and there's no need to work around that.

Share this post


Link to post
Share on other sites
Quote:
Original post by phresnel
* use, ahem, const_cast<>, or better, don't.


Why? This is what I have traditionally used in the past to get around this kind of borderline area. Would making the variable mutable be a better approach?

Share this post


Link to post
Share on other sites
Quote:

const-correctness is about the immutability of the object's state, not its individual members. An object is considered immutable even if some internally used attributes change but the object's state appears to be unchanging from an external point of view. This is perfectly fine and there's no need to work around that.


right-o. I think I'll stick with it. It's one of the C++ quirks.

I prefer mutable on individual members, over type casts that discards type qualifiers, or not using a constant qualifier at all.

The 'read' function is just an example. Reading from a file, your are not modifying it (it can be read-only and still works). I suppose the analogy (probably wrong) would be a read-only file, but attributes can change (obviously, the write-protected flag needs to be modifiable).

The problem with const is that it has a domino effect. Once you use it somewhere, it is likely it will affect other function declarations.

Share this post


Link to post
Share on other sites
Any time you feel that some language construct does not work the way you would like is probably an indication that you're trying to do something wrong.

In this case, you've made some functions const yet they modify the state of your object. Fact is, reading and wring your stream modifies the state of your stream, so they are not candidates for being const functions. Simple as that. No need to use mutable.

If you're going to reinvent what the language provides for you you might consider benefitting from the experience and wisdom of those who designed the laguage. For example, C++ provides stream classes with read() and write() functions, and yet those functions of the standard classes are not const. Why not?

Share this post


Link to post
Share on other sites
Quote:
Original post by _moagstar_
Quote:
Original post by phresnel
* use, ahem, const_cast<>, or better, don't.


Why? This is what I have traditionally used in the past to get around this kind of borderline area. Would making the variable mutable be a better approach?


I think the standard allows const_cast to reference and then changing the value of the referencee if the referencee happens to be non-const (i.e. not declared with const), but I personally think that const_cast's are plain ugly, the cast will always work (uninteded harddisk formatting or not), never mind whether the casted value is really const or not. But if I don't use const_cast, but instead declare the variable to be mutable, and then someday decide it shall no longer be mutable, the compiler will direct me to the points where I try to assign to it. But with a const_cast, if I happen to make some variable really const, the compiler won't bite me, and under uncertain conditions the application may crash.

Valid code:
class X {
int x;
void f () const {
const_cast<int&> (x)++;
}
};


But then, Joey finds that x'd better be const (even if he's missassuming something here):

class X {
const int x;
void f () const {
const_cast<int&> (x)++;
}
};


It still compiles, hopefully with a warning diagnostic, but it may crash freely during execution. Using mutable to state that you mean "x might change even in const function" might have prevented Joey from making it x const, because somebody else explicitly stated "x might change in const functions".

So, my opinion is that using const_cast for the sake of (alleged) const correctness is wrong, because there's potentially more trouble you can get in than with mutable.

Share this post


Link to post
Share on other sites
Quote:
Original post by oliii
The problem with const is that it has a domino effect. Once you use it somewhere, it is likely it will affect other function declarations.


Exactly. Begin soon with const-correctness, and imho, write RAII classes, which helps a lot with const-correctness (you can often make all members const if you don't have Init() or SetScreenSize(int,int) functions, but that is also POV).

Share this post


Link to post
Share on other sites
Quote:
Original post by oliii
I'm not a big fan of the 'mutable' storage class specifier. However, it seems inevitable, for example in a sort of stream base class.


I once went down this road for serialization, and it didn't end well. The confusion between const and mutable semantics just resulted in lots of const_casts and similar.

The biggest conflict arose in type resolution function for serializing arbitrary types, which was something like:
// if no other overloads match
template < class T > void write(Archive & a, const T & value) {
value->serialize(a);
}
This inevitable results in clash between const and mutable instances. I solved this by providing non-const versions of value, but then problems occurred with temporaries and some other things IIRC.

Lesson learned was not to try to circumvent the const-ness checks. Read mutates the class, so it cannot be const. Not trying to circumvent that will save you headaches later.

Quote:
Is there any clean way round it (while still maintaining the read() method to be declared 'const').


Not really. Same problem goes for intrusive serialization methods. I hoped to be able to use 'write() const', since writing to stream doesn't modify an object, but it results in too many corner cases, especially once temporaries come into play.

IMHO, just go with non-const versions.

Quote:
I suppose the analogy (probably wrong) would be a read-only file, but attributes can change (obviously, the write-protected flag needs to be modifiable).


Unfortunately, you are changing file pointer. The proper model would be:
class ReadOnlyFileReader {
char * offset;
const char data[];
};
Streams are not file - they are readers, and change, regardless of contents. By consequence, a reader cannot be const.

Share this post


Link to post
Share on other sites
The problem is that C++ doesn't have an easy idiom for "this is a class whose purpose is to refer to another chunk of data. The data I refer to will not be modified by this operation."

Some code uses the const keyword to refer to this.

You can see this in the iterator semantics: there are 4 types of iterator (yes, there are more, but)
iterator -- an iterator that can be changed, to non-const data
const iterator -- an iterator that cannot be changed, to non-const data
const_iterator -- an iterator that can be changed, to const data
const const_iterator -- an iterator that cannot be changed, to const data

With pointers, the syntax is simple:
foo* -- a pointer to a non-const foo
foo const* -- a pointer to a const foo
foo* const -- a const pointer to a non-const foo
foo const* const -- a const pointer to a const foo

But without similar help from the language, doing this with other classes that happen to 'really be' referring to external data becomes annoying.

You can do some of this with template code: but even there it is quite awkward.

Even worse than this, there is the distinction between "data that cannot change" and "data that you are not allowed to change". C++ lacks the "data that cannot change" version of const in the type system.

...

And the result? People punt. People create classes, like streams, where a const stream means "the data the stream is referring to is non-mutable by this stream".

It is a cludge. You can take a const stream and turn it into a non-const stream via copy constructor (something you don't want to be allowed!) You have to make internal state mutable in order to violate the rules of const correctness.

You are trying to be a T const*, but you said you are a T* const. And the language fights back.

Now, you could do something like this:

enum {Const, Non_Const} constFlag;

template<constFlag flag = Non_Const>
class stream;

template<>
class stream<Const> {
// const version of stream
};

template<Non_Const>
class stream: stream<Const> {
// non-const version of stream
};

Then, you have stream<Const> and stream<> as two base types.

Anything that is a stream<> is a stream<Const> and can be passed as such.

The methods that don't change the state of the buffer being referred to (like read) get stuck in stream<Const>.

Methods that don't change the state of the stream object are declaired const.

So now you have:

stream<> // mutable data, mutable stream state
stream<> const // mutable data, non-mutable stream state
stream<Const> // non-mutable data, mutable stream state
stream<Const> const // non-mutable data, non-mutable stream state


which might be an appropriate approach that doesn't fight the language.

Share this post


Link to post
Share on other sites
Quote:

This inevitable results in clash between const and mutable instances. I solved this by providing non-const versions of value, but then problems occurred with temporaries and some other things IIRC.

Lesson learned was not to try to circumvent the const-ness checks. Read mutates the class, so it cannot be const. Not trying to circumvent that will save you headaches later.


Yes, it's a bit of a mess. That's why I often tend to ignore const especially when polymorphism/templates are involved and you don't know what exact behaviour a const cast will have on an object.

Share this post


Link to post
Share on other sites
That approach is taken by the C++ standard library in <iostream>, only without the template (over?)parameterization. std::iostream inherits from std::istream. And std::ostream, but let's ignore that :P

const std::istream& -- constant position, constant data
const std::iostream& -- constant position, mutable data
std::istream& -- mutable position, constant data
std::iostream& -- mutable position, mutable data

both read() and write() would be non-const methods, since they mutate the current position of the stream. readat() and writeat() could both be const methods, both logically and code-wise without making the position a mutable variable if done right.

Share this post


Link to post
Share on other sites
Quote:
Original post by phresnel
Quote:
Original post by oliii
The problem with const is that it has a domino effect. Once you use it somewhere, it is likely it will affect other function declarations.


Exactly. Begin soon with const-correctness, and imho, write RAII classes, which helps a lot with const-correctness (you can often make all members const if you don't have Init() or SetScreenSize(int,int) functions, but that is also POV).


But it's not a "problem". It helps us silly humans avoid problems - assuming we can figure out when the right time to actually use const would be. I've found the only time I really use mutable is when internally caching results... then it's indespensible.

And to address the topic... I definitely agree it's not a const function - though now that means I need to look at some production code here at work which I wrote using const under the same situation!

Share this post


Link to post
Share on other sites
Quote:
Original post by popsoftheyear
Quote:
Original post by phresnel
Quote:
Original post by oliii
The problem with const is that it has a domino effect. Once you use it somewhere, it is likely it will affect other function declarations.


Exactly. Begin soon with const-correctness, and imho, write RAII classes, which helps a lot with const-correctness (you can often make all members const if you don't have Init() or SetScreenSize(int,int) functions, but that is also POV).


But it's not a "problem".


And I haven't claimed so ;)

Actually, one of the things I miss in many other programming languages is C++'s "const-system", because I am a great friend of const-correctness and RAII principles.

Share this post


Link to post
Share on other sites
I suspect that the internal state changes when calling read such that calling it a second time may produce a different result. That would mean it is not const.
However there isn't enough in the example given to clearly prove that. In fact all members are private so as written, the class has no obserable state.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this