【问题标题】:C++ array subscriptionC++ 数组订阅
【发布时间】:2013-12-05 15:09:53
【问题描述】:

我在通过数组订阅和分配获得所需行为时遇到问题。

有没有办法判断赋值是否与数组订阅一起使用?

编辑 我的问题可能应该是,我可以将 [] 映射到 getter,并将 []= 映射到 setter

// Expect this to return a reference to the value if the key exists, 
// or throw an exception if not
myMap["Key"];

// Expect this to always return a reference to the value
// so the value can be populated
myMap["Key"] = "Value";

// The method being used
template <typename K, typename V>
V& MyMap<K, V>::operator[](const K &key)
{
    if(this->keyExists(key))
    {
        return this->find(key);
    }
    else
    {
        // At this point I'd like to throw an exception if
        // assignment is not being used
        this->insert(key, NULL);
        return this->pairs[this->itemsStored].val;
    }

};

【问题讨论】:

  • 这是一个数组还是std::map
  • myMap的类型是什么?
  • 这是我自己的地图实现,可惜不能使用std::map
  • std::map 不区分,所以我不会费心尝试自己做。您可以通过返回一个临时对象来跳过燃烧的箍,如果它的operator = 尚未被调用(尽管您不应该从析构函数中抛出)并且该对象具有到值类型的隐式类型转换,则该对象会在销毁时引发异常。 .. 但我宁愿接受 C++ 不区分读取和写入 operator[]
  • @JohnKugelman :你可以制作const 和非const 版本的operator[] ......但这可能比它的价值更麻烦。

标签: c++ operator-overloading


【解决方案1】:

有没有办法判断赋值是否和数组一起使用 订阅?

简单地说,不。

当你这样做时:

myMap["Key"] = "Value";

您正在调用两个成员函数:首先,map::operator[],然后——在一个完全不同的类上——key::operator=。当您在没有分配的情况下简单地执行myMap["Key"] 时,您与地图的交互方式没有任何改变。唯一的区别是你要做什么下一步

我想,你可以找到一些技术技巧(比如提供做不同事情的const 和非const 版本),这将提供你想要实现的行为——但它会在糟糕的设计成本。由于您已在非常量版本中规定将添加缺少的密钥,因此随后加入非const 版本是一个主要区别。这将是一场噩梦。当您期望调用另一个版本时实际调用一个版本时,您将出现非常奇怪的错误。使用您的代码的人会感到困惑并诅咒您的名字。 不要这样做。

相反,我建议你一开始就找错了树。与其尝试使用operator[] const 来确定键的存在,不如简单地提供一个成员函数来完成此操作?

如果您愿意,可以在密钥不存在时使用此函数throw,或者直接返回bool

【讨论】:

  • 其实他想要做的很简单。这就是代理的用途。
【解决方案2】:

只有一种方法可以确保在operator[] 之后不会调用operator=(通过重载operator[] const 函数),但这并不是每次都有效。

由于没有(简单的)方法可以知道何时使用operator= 调用operator[],因此我建议您通过提供两个不同的函数来遵循std::map 的示例:

  • operator[],它总是返回对对象的引用;如果对象不存在,则创建它
  • at,仅当对象已经存在时才返回引用,否则抛出 std::out_of_range 类型的异常

【讨论】:

  • 实际上,使用代理,很容易捕捉到[],然后是赋值。一个更困难的问题是,如果您还需要处理 [key].nonConstFunction() 之类的事情。
  • @JamesKanze,对于 OP 来说,使用 代理 听起来并不像 简单 任务。
  • 应该是:-)。这是一种非常标准的 C++ 技术。
【解决方案3】:

是的。只需让operator[] 返回代理即可。就像是 以下应该工作。 (我使用std::map 执行;你可以将它映射到你正在使用的任何东西上)

template <typename KeyType, typename MappedType>
class MyMap
{
    std::map<KeyType, MappedType> myImpl;
//  ...
public:
    void set( KeyType const& key, MappedType const& value )
    {
        myImpl[key] = value;
    }
    MappedType get( KeyType const& key )
    {
        auto entry = myImpl.find( key );
        if ( entry == myImpl.end() ) {
            throw DesiredException();
        }
        return entry->second;
    }

    class Proxy
    {
        MyMap* myOwner;
        Key myKey;
    publc:
        Proxy( MyMap& owner, Key const& key )
            : myOwner( &owner )
            , myKey( key )
        {
        }
        Proxy const& operator=( MappedType const& value ) const
        {
            myOwner->set( myKey, value );
            return *this;
        }
        operator MappedType() const
        {
            return myOwner->get( myKey );
        }
    };
    Proxy operator[]( KeyType const& key )
    {
        return Proxy( *this, key );
    }
    MappedType operator[]( KeyType const& key ) const
    {
        return get( key );
    }
};

不过,我不确定这是个好主意。一般来说, 有一个get( KeyType ),它返回一个指向映射的指针 元素,或者如果它不存在则为空指针,似乎更多 在 C++ 中自然。

【讨论】:

    【解决方案4】:

    免责声明:以下是不好的做法,我不建议实际使用它。这只是为了表明 OP 想要的东西在技术上是可行的,即使这是一个非常糟糕的主意(原因我将进一步讨论)。

    坏主意

    如果您不介意有点麻烦,您可以拥有 const 和非 const 版本的 operator[],它们的行为会有所不同。 const 版本在访问不存在的项目时会抛出异常,而非const 版本会在这种情况下默认构造一个新项目。

    作为概念证明:

    #include <iostream>
    #include <stdexcept>
    
    class Map {
        public :
            int value;
    
            Map() { value = 42; }
    
            const int& operator[](const size_t& pos) const { if (pos == 0) return value; else throw std::runtime_error("oops"); }
            int& operator[](const size_t& pos) { return value; }
    };
    
    void showValue(const Map& myMap, size_t pos) {
        try {
            std::cout << "myMap[" << pos << "] = " << myMap[pos] << std::endl;
        }
        catch (std::runtime_error e) {
            std::cout << "exception when accessing myMap[" << pos << "] : " << e.what() << std::endl;
        }
    }
    
    int main(void) {
        Map myMap;
        showValue(myMap, 0);
        showValue(myMap, 1);
        myMap[0] = 5;
        showValue(myMap, 0);
        myMap[1] = 10;
        showValue(myMap, 0);
        return 0;
    }
    

    会打印:

    myMap[0] = 42
    exception when accessing myMap[1] : oops
    myMap[0] = 5
    myMap[0] = 10
    

    但我之前提到的麻烦是确保在结果不会被修改时使用const 版本(在上面的示例中,这是通过使用const 引用完成的)。

    为什么不好

    如开头所述(并且正如 John Dibling 在 cmets 中指出的那样),不推荐这种方法。问题是:

    • 很难知道将调用哪个版本的operator[](在可以同时调用两者的情况下)
    • operator[] 的两个版本的行为不同(一个会抛出异常,而另一个会在使用相同的参数调用时添加一个新项目)

    结合这两个观察结果,您将接近不可预测的行为,这会在您最意想不到的时候伤害您(相信我)。更糟糕的是,当此类问题发生时,可能难以追踪和解决。

    根据经验,任何成员函数的const 和非const 版本在其核心功能上不应有差异。违反该规则,你会激怒任何必须维护代码的人(这可能包括你未来的自己)。

    还有其他选择吗?

    所以,不要这样做。相反,只需按照 std::map 的方式进行操作(或者更好的是,使用 std::map),或者使用 contains 函数来检查项目是否存在。

    【讨论】:

    • 拥有const 和非const 版本的相同函数具有明显不同的行为是一个糟糕的设计。
    • @JohnDibling :我完全同意,但问题不在于好的设计,而是是否有可能(或者至少我是这么理解的)。
    • 不确定为什么投反对票?感谢您展示如何完成。我同意这很混乱
    • @SanderDeDycker:这个问题可能并不是专门针对设计的好坏,但这无关紧要。你教会了一个新手一个不存在的问题的拙劣技巧,并暗示(通过遗漏)这个设计没有任何问题。现在 PaulK 或其他未来的读者将继续编写糟糕的代码,而不考虑他们所做的事情的负面影响。非常短视。顺便说一句,我没有对你投反对票。我认为我的批评已经足够了。
    • 换句话说,在 SO 上提出的 每个 问题都应该由比提出问题的人更有经验的人回答时考虑到良好的设计。他们还将如何学习?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-18
    • 2016-12-03
    • 2015-10-03
    • 1970-01-01
    相关资源
    最近更新 更多