定期使用const_cast作为设计工具是否可以接受
is using const_cast regularly as a design tool acceptable?
我回顾了下面的代码,虽然我对这个问题有一个个人的答案(*),但我希望能得到c++/设计专家的评论。
由于某些原因,Data
是一个具有不可修改标识符和可修改值的对象:
class Data
{
const Id m_id ; // <== note that m_id is a const member variable
Value m_value ;
Data(const Id & id, const Value & value) ;
Data(const Data & data) ;
Data & operator = (const Data & data) ;
// etc.
} ;
设计选择变成了语言选择,因为标识符在类级别(**)被声明为const
,以避免它的(意外)修改,甚至从类成员函数内部…
…但是正如您所看到的,这里有一个复制赋值操作符,实现为:
Data & Data::operator = (const Data & that)
{
if(this != &that)
{
const_cast<Id &>(this->m_id) = that.m_id ;
this->m_value = that->m_value ;
}
return *this ;
}
复制赋值操作符不是const限定的这一事实使得这段代码是安全的(用户只能合法地在非const对象上调用该方法,而不会引发未定义行为)。
但是使用const_cast
来修改其他const成员变量在c++中是一个很好的类设计选择吗?
我想强调以下几点:
- 数据显然是一个值类型(它有一个
operator =
成员函数) - 在这种模式中,其他一些函数也可能合法地需要
const_cast
(例如移动构造函数/赋值和/或交换函数),但不是很多。
注意,这可能是一个代码审查问题,但这不是一个"日常"的代码。这是一个通用的c++类型设计问题,需要平衡语言的需求/能力和模式/习惯用法的代码解释。
还请注意,mutable
(如c++ 98)并不是解决这个问题的方法,因为其目的是使成员变量尽可能不可修改。当然,mutable
(就像Herb Sutter在c++ 11 post-"你不知道const
和mutable
")更不是一个解决方案。
(*)我可以私下把我对那个问题的答案转发给任何问我的人。
(**)另一个解决方案是使对象非const,并在接口级别使其为const(即不提供可以改变它的函数)
引用自cppreference:
尽管const_cast可以从任何指针或引用中删除constconstness或volatile,但是使用生成的指针或引用对声明为const的对象进行写操作或访问声明为volatile的对象会调用未定义的行为。
这意味着你的拷贝赋值是不安全的,而是完全不正确的。如果您声明某个东西为const
,则无法安全地更改它。这与设计无关。
const_cast
的唯一有效用途是从const引用或指向非const对象的指针中删除constness(或者指向const对象但不修改它,但这样你就可以不使用const_cast
)。
我将在实现私有成员的唯一访问器时使用它,当它返回const引用时,即该类的客户端只看到const引用。
然而,当派生类需要"修改"私有成员时,我可以实现一个非const保护的访问器,但我宁愿将派生类的访问器调用限制为const引用,因为大多数情况下它只需要const引用。
因此,在我确实需要在派生类中"调整"它的少数情况下,const_cast<>像疼痛的拇指一样突出,但这是我的选择。我喜欢它的突出。我可以很容易地搜索它(谁是const_cast<>-ing这个类?)。另一种选择——提供一个受保护的非常量访问器,可能在语法上更"正确",但我宁愿让非常量访问变得突兀,而不是"普通"。
通常情况下,类应该完全控制并了解自己的成员。保护成员在其自己的类中不被滥用的要求违背了一些基本的面向对象设计原则。
当然可以将私有变量声明为常量,如果它确实是常量。但在您的情况下,您只想保护它不受一些方法的影响。在这种情况下,保持它为非常量,或者拆分类。您可以使用类似Private类数据模式的东西来更好地控制变量的可访问性。
即使我不是设计专家,更不是c++专家,我也认为这是一个"设计陷阱"的例子(我允许自己这么说,因为这个陷阱确实很巧妙)。
在我看来,争论从"数据显然是一种值类型"的错误假设开始,然后变成了一些"constness"问题。
Data
对象的"value"是ID
和Value
的组合,Id
对Value
的"keyability"决定了(Id
, Value
)对的唯一性。换句话说,是Id-> Value对应关系将自身表征为常数,但在意义上,是惟一的。此外,如果Data
对象出生时是Id->值对应,由于某种原因不再有效(在必须修改的意义上),那么Data
本身已经结束了它的生命周期,因此它不会更改。从这个角度出发,我们来讨论不可变对象的特征。
KeyedValue
类模板通过从引用返回的对象池中绘制来封装上面概述的需求:
template <class K, class V>
class KeyedValue {
public:
typedef K key_type;
typedef V value_type;
const K& key() const { return _key; }
const V& value() const { return _value; }
operator K() const { return _key; }
//bool operator == (const Keyable& other) { return _key == other.key(); }
/**************************/
/* _value doesn't take part to hash calculation */
/* with this design choice we have unique KeyedValue(s) */
struct hash {
size_t operator()(const KeyedValue& d) const noexcept {
return std::hash<K>()(d.key());
}
};
/**************************/
static KeyedValue getValue(const K& key, const V& val));
private:
KeyedValue& operator = (const KeyedValue&); // Don't implement
K _key;
V _value;
protected:
KeyedValue(const K& key_val, const V& val): _key(key_val), _value(val) {}
static std::unordered_set<KeyedValue<K, V>, typename KeyedValue<K, V>::hash> value_pool;
};
template <class K, class V>
std::unordered_set<KeyedValue<K, V>, typename KeyedValue<K, V>::hash>
KeyedValue<K, V>::value_pool;
template <class K, class V>
KeyedValue<K, V> KeyedValue<K, V>::getValue(const K& key, const V& val) {
KeyedValue to_find(key, val);
auto got = value_pool.find (to_find);
if (got == value_pool.end()) {
value_pool.insert(to_find);
return to_find;
}
else
return *got;
}
typedef size_t Id;
typedef int Value;
typedef KeyedValue<Id, Value> Data;
- 是否有一种工具可以识别一组C++源文件所指的外部标识符
- 是否有 lint 工具可以检查子类虚拟函数是否与父类定义匹配?
- 在一个VS项目中使用多个工具集 - 是否存在风险
- AWS CPP S3 开发工具包是否支持"传输加速"
- 是否有用于可视化缓存的工具C++
- Apple C 模块进入 Xcode 工具链是否有时间表
- 您是否知道使用非恢复构建方法来管理构建的任何构建工具
- 是否有一个工具来记录代码执行
- 是否有任何工具支持C++基于检查点的内存使用情况分析
- 是否有任何工具可以警告可能滥用删除或删除[]
- 是否可以将依赖项嵌入到命令行工具中
- 是否有任何工具可以重构C++以符合编码标准
- 是否有任何工具可以显示函数中的数据依赖关系
- 是否可以使用 opencv 的 imshow 在没有工具栏的情况下显示图像?
- 是否有类似Valgrind Memcheck的工具,用于在没有错误后进行调试使用
- 是否有工具可以在定义的开关下剥离代码
- 是否有任何c++静态分析工具来检测向量的潜在错误
- 是否有可靠的工具可以删除ASM/C/C++代码中的注释
- 在一个解决方案中是否可以有具有不同平台工具集的项目?(MSBuild)
- 是否存在处理预处理器指令并给出实际预处理器输出的工具