1

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.

Community
  • 1
  • 1
user541686
  • 205,094
  • 128
  • 528
  • 886

3 Answers3

2

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.

Eran
  • 21,632
  • 6
  • 56
  • 89
  • Well, the file name is queried from the *system* each time, and it could change for the duration of the program (e.g. because the user changed it by hand, without my knowledge). That's why I update the value inside `getName` -- there's no need for `setName` to actually change the value. – user541686 Sep 28 '11 at 06:52
  • @Mehrdad, I wasn't aware of that, updated my answer accordingly. – Eran Sep 28 '11 at 07:05
0

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.

K-ballo
  • 80,396
  • 20
  • 159
  • 169
  • They might be surprised, but should they be? `const` does **not** imply that the data is *immutable*, but that it can't be changed *through* the `const` reference. – user541686 Sep 28 '11 at 06:28
  • @Mehrdad: They will, at least **I** will. If the data can be changed from outside the application then I would expect to see `volatile`. – K-ballo Sep 28 '11 at 06:32
  • See my example in my post. It's **very** easy for something `const` to change, it requires nothing fancy with `mutable` or whatever -- and it's certainly 100% consistent with the rules (and expectations) of `const`, which have nothing to do with immutability. – user541686 Sep 28 '11 at 06:43
  • @Mehrdad: At first, I was going to disagree with you. But after thinking about it some more, I actually agree. If we think about this as some sort of simple Model-View situation, then it makes sense. Just because the view is `const`, doesn't mean that the underlying data in the model will never change. It just means that the user can't use the view to change the model. – Ken Wayne VanderLinde Sep 28 '11 at 06:43
  • @K-ballo: Hmmm... I don't *quite* understand, though. Is this really a volatility issue, or an aliasing issue? I don't see why the compiler should need to re-evaluate the expression, because it's certainly not changing "beyond the scope" of the compiler. – user541686 Sep 28 '11 at 06:44
  • @KenWayneVanderLinde: Yup, exactly. :) This was [the exact reason why D introduced `immutable`](http://digitalmars.com/d/2.0/const3.html), btw. – user541686 Sep 28 '11 at 06:44
0

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.

Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
  • It's not quite independent if `getname` is `const`, because otherwise `getname` couldn't update the internal buffer... right? – user541686 Sep 28 '11 at 06:49
  • The getname method could be implemented in other ways that had the same behavior without using mutable. For example, it could return a pointer to a static variable. – Vaughn Cato Sep 28 '11 at 07:02