I reviewed the kind of code below, and while I have a personal answer to the question (*), I'd like to have comments from C++/design experts.
For some reason, Data
is an object with a non-modifiable identifier, and a modifiable value:
class Data
{
const Id m_id ; // <== note that m_id is a const member variable
Value m_value ;
Data(const Id & id, const Value & value) ;
Data(const Data & data) ;
Data & operator = (const Data & data) ;
// etc.
} ;
The design choice became a language choice as the identifier was declared const
at class level (**), to avoid its (accidental) modification even from inside the class member functions...
... But as you can see, there is a copy-assignment operator, which is implemented as:
Data & Data::operator = (const Data & that)
{
if(this != &that)
{
const_cast<Id &>(this->m_id) = that.m_id ;
this->m_value = that->m_value ;
}
return *this ;
}
The fact the copy assignment operator is not const-qualified makes this code safe (the user can only legally call this method on a non-const object without provoking undefined behavior).
But is using const_cast
to modify an otherwise const member variable a good class design choice in C++?
I want to stress the following:
- Data is clearly a value type (it has an
operator =
member function) - in this pattern, a few other functions could legitimately need
const_cast
, too (move constructors/assignment and/or swap functions, for example), but not a lot.
Note that this could have been a code review question, but this is not a "day-to-day" code. This is a general C++ type design question, with the need to balance the needs/power of a language and the code interpretation of patterns/idioms.
Note, too, that mutable
(as in C++98) is not a solution to the problem, as the aim is to make a member variable as unmodifiable as it can be. Of course, mutable
(as in C++11 post-"you don't know const
and mutable
" by Herb Sutter) is even less a solution.
(*) I can privately forward my answer to that question to anyone who asks.
(**) Another solution would have been to make the object non-const, and making it const at the interface level (i.e. not providing functions that can change it)