C++ 定期使用const_cast作为设计工具是否可以接受?

C++ 定期使用const_cast作为设计工具是否可以接受?,c++,const-cast,C++,Const Cast,我在下面回顾了这类代码,虽然我对问题(*)有个人的答案,但我想听听C++/设计专家的意见 出于某种原因,数据是一个具有不可修改标识符和可修改值的对象: 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) ;

我在下面回顾了这类代码,虽然我对问题(*)有个人的答案,但我想听听C++/设计专家的意见

出于某种原因,
数据
是一个具有不可修改标识符和可修改值的对象:

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.
} ;
复制赋值运算符不符合常量的事实使该代码安全(用户只能合法地对非常量对象调用此方法,而不会引发未定义的行为)

但是使用<代码> conconsCAST < /C> >修改一个const成员变量,C++中的一个好的类设计选择 我想强调以下几点:

  • 数据显然是一种值类型(它有一个
    操作符=
    成员函数)
  • 在这种模式中,其他一些函数也可以合法地需要
    const_cast
    (例如,移动构造函数/赋值和/或交换函数),但不是很多
请注意,这可能是一个代码审查问题,但这不是一个“日常”代码。这是一个通用的C++设计问题,需要平衡语言的需要/能力和模式/成语的代码解释。 还要注意的是,
mutable
(如在C++98中)并不是问题的解决方案,因为其目的是使成员变量尽可能不可修改。当然,
mutable
(就像C++11文章中赫伯·萨特的“你不知道
const
mutable
”)更不是一个解决方案

(*)我可以私下将我对该问题的回答转发给任何提问的人

(**)另一个解决方案是将对象设为非常量,并在接口级别设为常量(即不提供可以更改它的函数)

引用自

即使const_cast可以从任何指针或引用中删除constness或volatile,但使用结果指针或引用写入声明为const的对象或访问声明为volatile的对象会调用未定义的行为

这意味着您的副本分配不安全,但显然不正确。如果您声明某个
const
,则无法安全地更改它。这与设计无关


const_cast
的唯一有效用途是从常量引用或指向非常量对象(或指向常量对象,然后不修改它,但这样你就不能
const_cast
).

我将在实现私有成员的单独访问器时使用它,其中它返回一个常量引用,即该类的客户端只看到一个常量引用

然而,当派生类需要“修改”私有成员时,我可以实现一个非常量保护的访问器,但我更愿意将派生类的访问器调用限制在常量引用上,在大多数情况下它只需要常量引用

因此,在我确实需要在派生类中对其进行“调整”的少数情况下,const_cast会像拇指疼痛一样突出,但这是我的选择。我喜欢它突出。我可以很容易地搜索它(谁在主持这个课程?)


另一种选择是,提供一个受保护的非常量访问器,在语法上可能更“正确”,但我更愿意让非常量访问变得突兀,而不是“普通”。

通常一个类应该完全控制并了解它自己的成员。保护成员在其自己的类中不被误用的要求违背了一些基本的面向对象设计原则


当然,如果私有变量真的是常量,那么可以将其声明为常量。但在您的情况下,您只想保护它不受某些方法的影响。在这种情况下,保持它为非常量,或者拆分类。你可以使用类似的东西来更好地控制变量的可访问性。

< P>即使我不是设计专家,更不用说C++专家,我认为这是一个“设计陷阱”(我允许自己说,因为陷阱是巧妙地完成的)。 在我看来,这场争论始于错误的假设,即“数据显然是一种价值类型”,然后演变为一些“恒定性”问题

数据
对象的“值”是
ID
的组合,而
ID
的“可键性”决定了(
ID
)对的唯一性。 换句话说,它是Id->Value对应关系,将自身描述为常量,但在单音的意义上

此外,如果
数据
对象作为Id->值对应关系生成,由于某种原因不再有效(在必须修改的意义上),则
数据
本身已结束其生命周期,因此不会更改。从这个角度来看,我们来描述不可变对象的特性

我将使用以下代码实现它,其中
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;
模板
类键控值{
公众:
typedef K key_type;
类型定义V值_类型;
常量K&key()常量{return_key;}
常量V&value()常量{return_value;}
运算符K()常量{return\u key;}
//布尔运算符==(const-Keyable&other){return u-key==other.key();}
/**************************/
/*\u值不参与散列计算*/
/*通过此设计选择,我们拥有独特的KeyedValue*/
结构散列{
size_t运算符()(常量KeyedValue&d)常量noexcept{
返回std::hash()(d.key());
}
};
/**************************/
静态KeyedValue getValue(常量K和key,常量V和val));
私人:
KeyedValue&运算符=(const KeyedValue&);//不实现
K_键;
V_值;
受保护的:
键控值(常数K和键控值,co
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;