如何在操作符[]上分离get和set访问

How can I separate get and set access on operator[]?

本文关键字:get 分离 set 访问 操作符      更新时间:2023-10-16

EDIT我引用右值引起了一些混淆。我在底部提供了一个示例,说明我想要做什么。

我想把这两个调用分开:

obj[key] = value;
value = obj[key];

我尝试使用两个operator[]重载,希望const方法将专门用于右值,而非const版本将专门用于左值调用。

const ValueType& operator[] (const KeyType& key) const;
ValueType& operator[] (const KeyType& key);

但是我的const版本只在this是const时才被调用。

我尝试使用覆盖op=op ValueType的包装器。

BoundValue<KeyType, ValueType> operator[] (const KeyType& key)
// later in BoundValue
operator ValueType() const
const V& operator= (const V& value)

效果非常好…直到我希望op[]将隐式强制转换为模板参数推导(这是不允许的)。

// example failure of wrapper
// ValueType is string, std::operator<< from string is a template method
cout << obj[key];

对于这种特殊情况,我可以定义自己的ostream方法。

ostream& operator<< (ostream& os, const BoundValue<K, V>& bv);

但是我的类型同样会在任何用户定义的模板参数中失败(看起来很可能)。

我可以想到两种解决方法,这两种方法都大大降低了我试图提供的语法糖的甜度:

// example where ValueType is string
cout << static_cast<string>(obj[key]);

// added to BoundValue
const ValueType& Cast() const
// later in use
cout << obj[key].Cast();

期望用例的示例

Map<int, string> m;
m[1] = "one";
try
{
    string one = m[1];
    cout << "one is set: " << one << endl;
    string two = m[2];
    cout << "two is set: " << two << endl;
}
catch (...)
{
    cout << "An exception was thrown" << endl;
}
预期输出:

one is set: one
An exception was thrown

异常如何/为什么被抛出?我可以检查键是否存在,如果不存在就抛出。如果我不能把get和set分开,我就不知道什么时候允许访问一个键

operator[]返回一个引用(类)类型,实现了解引用(get)和突变(set)操作,operator*()operator=()。至少在理论上,您不需要返回真正的引用,只要您愿意做必要的工作,使返回的值(大部分)像引用一样。因此,您可以使用一种类型,在映射中存在键的情况下存储指向pair<const Key, Val>的指针,否则存储不存在的Key的副本。在后一种情况下,解引用操作符将抛出异常,而变异操作符将把键移到映射中。

保留键的副本以便在必要时将它们惰性地添加到映射中,这有点昂贵,但如果您真的想要该功能,则不必过分。一种可能的优化是始终将键添加到映射中,但不构造匹配的Val类型,也不允许map返回Key值。一种简单的实现方法是为每个键-值对分配一个位,指示键是否实际上在映射中。与前面一样,尝试解引用一个指向不在映射中的键值对的指针会引发异常,而对该值进行突变只会构造新值并设置有效位。引用操作符的析构函数实际上会从映射中删除无效键,但假设这种操作很少发生,因为根据定义,它是异常的。不幸的是,健壮的实现并没有那么简单,因为您需要考虑同时存在多个与同一个不存在的键对应的引用对象的可能性。如果值类型足够大,可以覆盖引用计数,那么引用计数方案是可能的,但我认为您需要担心数据竞争,除非您的operator[]被仔细记录。(引用计数不是微不足道的,因为有可能由于其中一个引用发生突变而使键变为有效。幸运的是,不需要维护有效键的计数,因此析构函数只需要在减少引用计数和/或删除无效键之前检查有效性。

上面的方法有几个缺点。一个是c++没有单一的变异操作符;它实际上有很多种类(+=, -=, <<=,等等,等等,还有++--)。所以会有很多样板文件

更严重的是,程序保留operator[]返回的值作为引用对象是完全合法的,并且许多程序员会假设返回的引用是一个简单的Val&。通常,此类代码的目的是能够在不执行多次查找的情况下执行多次突变。除了类型问题,这些天很容易用auto解决,这将"可能工作",但延迟插入可能会产生一些意外。

你试过了吗:

     Friend ostream& operator<< (ostream& os, const BoundValue<K, V>& bv);

当一个操作符在另一个类中被重载,而你想在你的类中重载该操作符时,你必须将你的函数声明为友元函数。就像使用运算符<<在"fstream "库中重载