Is this a good use for "mutable" in C++?
I have a class that wraps a file handle:
class FileHandle
{
HANDLE hFile;
TCHAR name[256];
public:
LPCTSTR getName() const { /*(query system for name)*/ return this->name; }
};
I have come up with a design choice:
Since I will be querying the file name often, in开发者_开发技巧 order to minimize heap allocations which would happen if I returned std::wstring
(I have repeatedly seen that this is a bottleneck in my programs), I have instead decided to keep a name
field inside the object itself, and just return a pointer to it as shown.
Of course, the name of the file can change over time, so I can't avoid querying it every time. I can only avoid the reallocation.
Of course, the section saying (query system for name)
would not work as shown because name
isn't mutable
.
On the one hand, the caller doesn't expect the file name to change. But on the other hand, that's not what const
means, anyway. The name can certainly change, it's just that the caller can't modify it. So it doesn't look like it should be a problem, but I'm not too sure.
Under what circumstances would it be a good idea for me to use mutable
here? Why?
Note 1: Windows guarantees that file names are at most 256 characters long, so there's no buffer overflow issue to consider here.
Note 2: The class is only designed for single-threaded use. I'm not worried about concurrent modifications, only modifications in between statements.
Why const
doesn't imply immutability:
This should be self-explanatory:
FileHandle file = ...;
const FileHandle &fileConst(file);
LPCTSTR name1 = fileConst.getName();
file.setName(_T("new name"));
LPCTSTR name2 = fileConst.getName();
Now name1
and name2
aren't equal. So not only can the file name change quite easily, but name1
itself can also change -- even though they're both const
. There's no rule saying that const
members can't change, just that they can't be changed through the const
reference.
The missing method here, as I see it, is setName
. There's no problem with your code as it is, because there's no way to change name
, and changing it via getName
is awkward. So, assuming you'd have some
void setName(LPCTSTR newName) { _tcscpy(name, newName); /*or so*/ }
Question now is how you'd expect it to be used. It makes sense that whoever is supposed to be changing the name, has access to a non-const FileHandle
. In such case, there's no problem using this trivial setName
. If, however, the file name should be changed on a const FileHandle
, you have two problems: first, it's awkward... and second, you won't be able to call the setName
above. To be able to call it, you'd have to change it to
void setName(LPCTSTR newName) const { _tcscpy(name, newName); /*or so*/ }
which doesn't really makes sense either, but let's pretend it does. Now, this won't work, because FileHandle
being const would effectively make name
const as well. And this finally brings us to mutable
: changing name
's declaration to:
mutable TCHAR name[256 + 1 /*for NULL terminator*/];
will indeed allow you to use setName
to change the name of a const FileHandle
. To me, this looks like a sign of a bad design, where you're actually hacking your own code. For that matter, you can just const_cast
the FileHandle
, and change it without using mutable
. But I really don't know the specifics of your case, so maybe it does make sense...
Update, given the information that getName
actually checks the name of the file, and updates name
if needed before returning it: in that case, making name
mutable would indeed be the way to go, because otherwise it can't be changed from within a const method. It is generally not advised for a getter to change the value of the member whose value it's getting, but if your case dictates that, then making name
a mutable
would make sense.
I would say it is acceptable, but not ideal. I think logically you have a name that is independent the file handle. You are not asking for the name of the file handle, you are asking for the name of the file that the file handle is referring to. Getting that name doesn't change the file handle, so the logical constness is preserved. What is not ideal is that the results could change in unexpected ways:
LPCTSTR old_name = filehandle.getname();
changeFileName(filehandle);
LPCTSTR new_name = filehandle.getname();
// but old_name and new name still match!
But that is really independent of whether or not the member should be made mutable.
Of course, the name of the file can change over time
Are you sure about that? Can a file name be changed while there is an open handle to it? If that's the case, then this is not a good use of mutable
. mutable
is there to achieve logical constness when bitwise constness cannot be achieved, like is the case with cached data. However, callers of your getName()
function would be surprissed if two consecutive const
calls would return different values.
Update:
If the property is expected to change from outside the program declaration, then you should make that statement by declaring the function const volatile
, and then the use of mutable
may be justified. However note there is another problem with this approach, in that a caller of the function keeps the pointer to the file name around and a subsequent call to the function will change its contents. That means that the result of this function should be considered volatile
as well.
Update 2:
There is no rule in the standard that dictates the uses of const
, no one will stop you from flagging all your functions const
and your members volatile
. However, const
is usually used to indicate that the logical constness of the object won't change by invoking a member function; and volatile
is usually used to indicate that the value may be changed from outside the application. The question is about good use of mutable
-which I consider subjective-, and mutable
is not an outsider to this particular use case specially if the function has a volatile
modifier as well. However, const volatile
modifiers are rare, const
functions returning different values after subsequent calls are rare, and values being changed outside your control are rare. Considering not only the function signature but the ammount of warnings that the documentation should include, I think that the surprise factor is high enough for this to be considered a bad use case at least in my book.
精彩评论