ES.50: Don't cast away const

ES.50:不要执行去掉const属性的转换

 

Reason(原因)

It makes a lie out of const. If the variable is actually declared const, modifying it results in undefined behavior.

这使代码避开了常量属性的约束。如果变量实际上具有常量属性,修改它会导致无定义的行为。

 

Example, bad(反面示例)

void f(const int& x)
{
const_cast<int&>(x) = 42; // BAD
}

static int i = 0;
static const int j = 0;

f(i); // silent side effect
f(j); // undefined behavior

Example(示例)

Sometimes, you may be tempted to resort to const_cast to avoid code duplication, such as when two accessor functions that differ only in const-ness have similar implementations. For example:

有时,你可能想借助const_cast来避免代码重复,例如两个编写实现相似,只有const属性不同的访问方法。例如:

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;
};

Instead, prefer to share implementations. Normally, you can just have the non-const function call the const function. However, when there is complex logic this can lead to the following pattern that still resorts to a const_cast:

更好的办法是共享实现方式。通常,你只要让非常量函数调用常量函数就行了。然而,如果逻辑很复杂,这种方式会导致下面的代码模式,还是需要借助const_cast。

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;
};

Although this pattern is safe when applied correctly, because the caller must have had a non-const object to begin with, it's not ideal because the safety is hard to enforce automatically as a checker rule.

这种方式在正确实现的情况下是安全的,但是调用者必须准备一个非常量对象。由于这种安全性很难通过自动执行的规则来保证,不够理想。

Instead, prefer to put the common code in a common helper function -- and make it a template so that it deduces const. This doesn't use any const_cast at all:

更好的方式是将共通代码放到一个共通的辅助函数中--并且将辅助函数设计为模板函数。这种方式不需要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) -> decltype(t.get_bar())
{ /* the complex logic around getting a possibly-const reference to my_bar */ }
};

Exception(例外)

You may need to cast away const when calling const-incorrect functions. Prefer to wrap such functions in inline const-correct wrappers to encapsulate the cast in one place.

如果调用一个不接受常量的函数,你可能需要通过转换去掉常量属性。最好将这样的函数封装在一个要求常量的行内包装函数内,以便在将这种转换限制在一个地方。

 

Example(示例)

Sometimes, "cast away const" is to allow the updating of some transient information of an otherwise immutable object. Examples are caching, memoization, and precomputation. Such examples are often handled as well or better using mutable or an indirection than with a const_cast.

有时,去掉常量属性的转换用于临时允许更新信息,其他情况下都是不可改变的对象。这样的例子包括缓存,备忘录和预处理等。对于这类情况,使用mutable或者间接操作也可以,甚至比使用const_cast更好。

Consider keeping previously computed results around for a costly operation:

考虑为了高成本计算而长期保存计算结果的情况:

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;
};

Here, get_val() is logically constant, so we would like to make it a const member. To do this we still need to mutate cache, so people sometimes resort to a const_cast:

这里,get_val()逻辑上应该是常量函数,因此我们希望将它设计成常量成员。为了做到这一点,我们还是需要修改缓存的内容,因此人们有时会借助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;
};

Fortunately, there is a better solution: State that cache is mutable even for a const object:

幸运的是,存在一个更好的解决方案:将cache声明为mutable(即使对于const对象也有效):

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:

另外一个解决方案是将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;
};

That solution is the most flexible, but requires explicit construction and destruction of *cache (most likely in the constructor and destructor of X).

这个方案更有弹性,但是需要cache对象的显式构造和析构(最有可能位置是X的构造函数和析构函数)

In any variant, we must guard against data races on the cache in multi-threaded code, possibly using a std::mutex.

对于所有变量,我们必须防止多线程编程时在cache上发生数据竞争,或许可以使用std::mutex。

 

Enforcement(实施建议)

  • Flag const_casts.
  • 标记const_cast.
  • This rule is part of the type-safety profile for the related Profile.
  • 这条准则是类型安全群组及相关群组的一部分。

 

原文链接

​https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es50-dont-cast-away-const​

 


 

觉得本文有帮助?欢迎点赞并分享给更多的人。

阅读更多更新文章,请关注微信公众号【面向对象思考】