CppCoreGuidelines ES.50 不要强制类型转换掉 const
“Don’t cast away const
”
理由
通过强制类型转换把 const
去掉,其实只是制造了一种变量可变的假象。如果实际变量是个常变量,但是你通过强制类型转换去掉了 const
,然后去修改它,结果是未定义的行为。
坏例子
void f(const int& x) { const_cast<int&>(x) = 42; // BAD } static int i = 0; static const int j = 0; int main() { f(i); // silent side effect cout << "i = " << i <<"\n"; f(j); // undefined behavior cout << "j = " << j <<"\n"; return EXIT_SUCCESS; }
i = 42
例子
有时候,你可能会考虑利用const_cast
避免重复代码。比如,两个访问函数,分别对应常量和非常量方式访问。
比如以下这种情况:
class Bar; class Foo { public: // BAD, duplicates logic Bar& get_bar() { /* complex logic around getting a non-const reference to my_bar */ } const Bar& get_bar() const { /* same complex logic around getting a const reference to my_bar */ } private: Bar my_bar; };
为了避免代码重复,一般可以让非常量访问的函数调用常量访问的函数。
class Foo { public: // not great, non-const calls const version but resorts to // const_cast Bar& get_bar() { return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar()); } const Bar& get_bar() const { /* the complex logic around getting a const reference to my_bar */ } private: Bar my_bar; };
如果实现正确,这个模式还是安全可行的。因为发起调用的函数保管一个非-const 的对象。但是不够完美。
另外一个办法,就是把共用部分的代码放进一个辅助函数里,把这个辅助函数设置成模板,这样就不需要const_cast
也能推导出两个版本:
class Foo { public: // good Bar& get_bar() { return get_bar_impl(*this); } const Bar& get_bar() const { return get_bar_impl(*this); } private: Bar my_bar; template<class T> // good, deduces whether T is const or non-const static auto& get_bar_impl(T& t) { /* the complex logic around getting a possibly-const * reference to my_bar */ } };
注意,不要在模板函数中放太多不相关的代码,不然会导致代码膨胀。比如说,后期如果发现有一些共用代码不需要做成模板,就可以提取出来,各自调用,而不要放进模板函数。这样可以降低生成重复的代码。
例外
在调用 const
不正确的函数时候,你可能会把 const
强制转换掉。最好把这些函数限制在一个 inline
的 const
正确的函数中,把类型转换限制在一个地方。
例子
有时候,把 const
转换掉是为了临时更新某个不可变对象。例子有:缓存
caching、备忘 memoization 、以及预计算 precomputation。不过,处理这些例子的更好的方法是使用 mutable
或间接访问。
考虑把之前耗费资源的操作计算的结果保存起来使用:
int compute(int x); // compute a value for x; assume this to be costly class Cache { // some type implementing a cache for an int->int operation public: pair<bool, int> find(int x) const; // is there a value for x? void set(int x, int v); // make y the value for x // ... private: // ... }; class X { public: int get_val(int x) { auto p = cache.find(x); if (p.first) return p.second; int val = compute(x); cache.set(x, val); // insert value for x return val; } // ... private: Cache cache; };
这里get_val()
逻辑上是个常数,所以我们可能想要把它设置为 const
成员。但是,依然需要修改 cache
,所以人们经常用到const_cast
:
class X { // Suspicious solution based on casting public: int get_val(int x) const { auto p = cache.find(x); if (p.first) return p.second; int val = compute(x); const_cast<Cache&>(cache).set(x, val); // ugly return val; } // ... private: Cache cache; };
幸运的是,有更好的方案:把 cache
设置为 mutable
。这样,就算对象 X
是个 const
,你依然能修改 cache
。
class X { // better solution public: int get_val(int x) const { auto p = cache.find(x); if (p.first) return p.second; int val = compute(x); cache.set(x, val); return val; } // ... private: mutable Cache cache; };
An alternative solution would be to store a pointer to the cache:
class X { // OK, but slightly messier solution public: int get_val(int x) const { auto p = cache->find(x); if (p.first) return p.second; int val = compute(x); cache->set(x, val); return val; } // ... private: unique_ptr<Cache> cache; };
这个解决方案更加灵活。但是需要显式地构造和析构*cache
。
不管什么情况,我们都需要确保多线程执行的时候, cache
线程安全,可能需要用到std::mutex
。
强化
- 标注
const_casts
- 此规则适用于类型安全指南集合