Reputation: 837
Lets say I have an immutable class C
. From a user perspective, we can never change the functional behavior of any C
object.
However, for performance reasons, lets say we have a toString
method that converts the object into a string and returns it. I don't want to have to do this computation each time, so I store the result in a member variable so if the user calls toString
again, it will be fast.
Do I make the toString
function const
(and just use const_cast
to store the result), because as long as we separate interface from implementation, toString
should be treated as not modifying the object, or should I make it non-const, because it will help the compiler catch errors?
Upvotes: 4
Views: 120
Reputation: 11317
Const has multiple usages, first of all, const is important for documentation. I know people disagree on this, though it clearly shows the intent of the member function or the parameters of a function.
Secondly, it prevents errors, when you have indicated the intent and you happen to call the wrong function (non-const) this will give compilation error. So either your intent is wrong or the function should not be called.
Finally, there are exceptions to the rule. Whenever you have some lazy implementation, you should use mutable, as adapting a mutable member is allowed (known in all translation units because in header file) while adapting a const-casted is undefined behavior (hidden in a single function implementation). Personally, I have mixed feeling about using mutable as you have to be careful in multithreading. (So you need either correct locking, std::call_once ... OR you need to know that this ain't called in multithreaded code)
More details on const-cast and mutable can be found in the standard in 7.1.6.1 and 5.2.11
Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior.
Often people refer to optimization via const. Though this is actually harder than you would expect. I've been following the clang-compiler on this: http://lists.llvm.org/pipermail/llvm-dev/2015-October/091178.html where you can see that currently only a proof-of-concept is implemented. (I'm not familiar with other implementations)
So in conclusion:
Upvotes: 1
Reputation: 245
The point of having a const object is to guarantee that it won't be changed over the course of some routine. This is entirely about communicating to other developers your intent and providing the optimizer with information since const doesn't exist at an machine code level.
Const objects only allow calling const methods, otherwise an object that's declared const might have its value changed by calling one of its methods which defeats the point of it being const in the first place. It is therefor good practice to label methods as const whenever possible when you are writing a class, so that const objects of that class can call those methods.
There are a few implementations that will give you what you want. Here is one design that should work for you, given what little I know about your current requirements. This creates the string in the constructor since the constructor can modify the object even if the object is declared const. Then it retrieves the pre-populated string whenever toString() is called and there is no need for the mutable keyword (more on this in the second example). I hope you don't mind the liberties I've taken with what sort of string to construct and what should actually go into it.
class ImmutableClass
{
private:
std::string strTextRepresentation;
public:
int nValue1, nValue2, nValue3; //variables that are to be used in the string
ImmutableClass(int nValue1, int nValue2, int nValue3):
nValue1(nValue1), nValue2(nValue2), nValue3(nValue3)
{
std::stringstream ss;
ss << nValue1 << ',' << nValue2 << ',' << nValue3;
strTextRepresentation = ss.str();
}
const std::string& toString() const
{
return strTextRepresentation;
}
};
As Chris Dodd mentioned in a comment above, you can also get what you want using the mutable keyword. The advantage would be that you could 'lazy-load' the string. This is a good choice if the overhead of constructing the string at the time your class is instantiated is too much to bear (perhaps because you need a lot of these classes and very few of them will ever have their toString() methods called).
class ImmutableClass
{
private:
mutable std::string strTextRepresentation;
public:
int nValue1, nValue2, nValue3; //variables that are to be used in the string
ImmutableClass(int nValue1, int nValue2, int nValue3):
nValue1(nValue1), nValue2(nValue2), nValue3(nValue3)
{
}
const std::string& toString() const
{
//NOTE: consider using another kind of check for if the string
//has been set. I recommend an optional<T> wrapper (easy to implement, or see the boost library)
if (strTextRepresentation.size() == 0)
{
std::stringstream ss;
ss << nValue1 << ',' << nValue2 << ',' << nValue3;
strTextRepresentation = ss.str();
}
return strTextRepresentation;
}
};
Either of those should work and honestly there are a few others but those others are not good for various reasons even if they technically work. Your idea of const_cast-ing the object is one of those other methods because you are declaring that an object be constant and then at that same level of abstraction saying that it isn't really constant in one case. const_cast should virtually never be used if there is any other choice.
Upvotes: 2
Reputation: 171
Why on earth corect answers end up in the comments...
It's OK to do have such a function. That approach is given in C++ FAQ "The trailing const on inspect() member function should be used to mean the method won’t change the object’s abstract (client-visible) state". Just dont use const_cast but mutable, see https://isocpp.org/wiki/faq/const-correctness#mutable-data-members.
Upvotes: 0