我有一个包装文件句柄的类:
class FileHandle
{
HANDLE hFile;
TCHAR name[256];
public:
LPCTSTR getName() const { /*(query system for name)*/ return this->name; }
};
我想出了一个设计选择:
因为我经常会查询文件名,以便最小化堆分配,如果我返回std::wstring
(我反复看到这是我的程序中的瓶颈)会发生,我决定将name
字段保留在对象本身内,然后返回指向它的指针,如图所示。
当然,文件的名称会随着时间的推移而改变,所以我无法避免每次都查询它。我只能避免重新分配。
当然,(query system for name)
部分不的部分如图所示,因为name
不是mutable
。
一方面,调用者不会期望更改文件名。但另一方面,无论如何,不 const
意味着什么。名称肯定会改变,只是调用者无法修改它。所以它看起来不应该是一个问题,但我不太确定。
在什么情况下,我在这里使用mutable
是个好主意?为什么呢?
注1:Windows 保证文件名长度最多为256个字符,因此这里没有要考虑的缓冲区溢出问题。 < / p>
注意2:该类仅用于单线程使用。我并不担心并发修改,只在语句之间修改。
const
并不意味着不变性:这应该是不言自明的:
FileHandle file = ...;
const FileHandle &fileConst(file);
LPCTSTR name1 = fileConst.getName();
file.setName(_T("new name"));
LPCTSTR name2 = fileConst.getName();
现在name1
和name2
不相等。因此,仅文件名不能轻易更改,但 name1
本身也可以更改 - 即使它们都是const
。没有规则说const
成员无法更改,只是无法通过const
引用更改。
答案 0 :(得分:2)
正如我所看到的,这里缺少的方法是setName
。你的代码没有问题,因为没有办法改变name
,而通过getName
进行更改是很尴尬的。所以,假设你有一些
void setName(LPCTSTR newName) { _tcscpy(name, newName); /*or so*/ }
现在问题是您希望如何使用它。有意义的是,谁应该更改名称,可以访问非const FileHandle
。在这种情况下,使用这个简单的setName
没有问题。但是,如果要在const FileHandle
上更改文件名,则会遇到两个问题:首先,它很尴尬......其次,您将无法调用上面的setName
。为了能够调用它,您必须将其更改为
void setName(LPCTSTR newName) const { _tcscpy(name, newName); /*or so*/ }
这也没有意义,但让我们假装它。现在,这不起作用,因为FileHandle
是const将有效地使name
const。这最终将我们带到mutable
:将name
的声明更改为:
mutable TCHAR name[256 + 1 /*for NULL terminator*/];
确实允许您使用setName
更改const FileHandle
的名称。对我来说,这看起来像是一个糟糕设计的标志,你实际上是在攻击你自己的代码。就此而言,您可以const_cast
FileHandle
,并在不使用mutable
的情况下进行更改。但我真的不知道你的具体情况,所以也许它确实有意义......
根据getName
实际检查文件名称的信息进行更新,并在返回之前根据需要更新name
:在这种情况下,使name
可变实际上是方式去,因为否则它不能从const方法中改变。通常不建议getter更改其值正在获得的成员的值,但如果您的情况决定了这一点,那么将name
设为mutable
将是有意义的。
答案 1 :(得分:0)
当然,文件的名称可能随时间而变化
你确定吗?文件名是否可以在打开句柄时更改?如果是这种情况,那么这不是mutable
的好用。当{strong>按位常量无法实现时,mutable
可以实现逻辑常量,就像缓存数据的情况一样。但是,如果两个连续的getName()
调用返回不同的值,则const
函数的调用者会感到惊讶。
<强>更新强>
如果预期该属性会从程序声明外部更改,那么您应该通过声明函数const volatile
来创建该语句,然后使用mutable
可以是有道理的。但请注意,这种方法存在另一个问题,即函数的调用者将指针保持在文件名周围,随后对函数的调用将改变其内容。这意味着此功能的结果也应该被视为volatile
。
更新2:
标准中没有规定使用const
的规则,没有人会阻止您标记所有功能const
和您的成员volatile
。但是,const
通常用于表示通过调用成员函数不会更改对象的逻辑const; volatile
通常用于表示可以从应用程序外部更改值。问题是关于mutable
的良好使用 - 我认为是主观的 - 并且mutable
不是这个特定用例的局外人,特别是如果函数有{{1}修饰符也是如此。但是,volatile
修饰符很少见,const volatile
函数在后续调用很少后返回不同的值,并且在控件之外更改的值很少。不仅要考虑功能签名,还要考虑文档应该包含的警告数量,我认为这个意外因素至少在我的书中被认为是一个不好的用例。
答案 2 :(得分:0)
我会说这是可以接受的,但并不理想。我认为逻辑上你有一个独立于文件句柄的名称。您不是要求文件句柄的名称,而是要求文件句柄引用的文件的名称。获取该名称不会更改文件句柄,因此会保留逻辑常量。不理想的是,结果可能会以意想不到的方式发生变化:
LPCTSTR old_name = filehandle.getname();
changeFileName(filehandle);
LPCTSTR new_name = filehandle.getname();
// but old_name and new name still match!
但这实际上与成员是否应该变得可变无关。