【问题标题】:Questions about operator overloading关于运算符重载的问题
【发布时间】:2011-01-19 03:16:06
【问题描述】:

我有两个关于运算符重载的问题。

  1. 对于迭代器类型,operator-> 是如何重载的?假设它是 class T 对象集合的迭代器,它应该返回什么值?

  2. 为什么operator++()class T& 返回,而operator++(int)class T 返回?我知道这两个代表前缀增量和后缀增量。但是为什么返回值不同呢?

编辑:对于阿尔夫。代码虽然可以运行,但并不完整。欢迎提出任何改进建议。

#ifndef DHASH_H
#define DHASH_H

//#include <vector>
#include <memory>
#include <exception>
#include <new>
#include <algorithm>
#include <functional>

namespace MCol
{
    template <typename KEY, typename VALUE, typename HASH_FUNCTION, typename KEY_COMP = std::equal_to<KEY> >
        class hash_container
        {
            private:
                struct entry
                {
                    KEY _k;
                    VALUE _v;

                    entry(const KEY& k, const VALUE& v)
                        :_k(k), _v(v)
                    {}

                    entry& operator=(const entry& e)
                    {
                        this->_k = e._k;
                        this->_v = e._v;
                    }
                };

            private:
                struct bucket
                {
                    entry* a_Entries;
                    size_t sz_EntryCount;   

                    bucket()
                    {
                        sz_EntryCount = 0;
                        a_Entries = NULL;
                    }

                    ~bucket()
                    {
                        for(size_t szI = 0; szI < sz_EntryCount; ++szI)
                        {
                            a_Entries[szI].~entry();
                        }
                        free(a_Entries);
                    }

                    //Grow by 1. (Perhaps later try block increment. But wikipedia suggests that there is little difference between the two)
                    inline bool insert(const KEY& k, const VALUE& v) throw (std::bad_alloc)
                    {
                        if(find(k) != NULL)
                        {
                            return false;
                        }
                        a_Entries = static_cast<entry*>(realloc(a_Entries, sizeof(entry)*(++sz_EntryCount)));
                        if(a_Entries == NULL)
                        {
                            throw std::bad_alloc();
                        }

                        new (&a_Entries[sz_EntryCount - 1]) entry(k, v);
                        return true;
                    }

                    //Find entry, swap with last valid entry, remove if necessary.
                    inline bool erase(const KEY& k) throw(std::bad_alloc)
                    {
                        //Forwards or backwards? My guess is backwards is better.
                        entry* pE = a_Entries;
                        while(pE != a_Entries + sz_EntryCount)
                        {
                            if(pE->_k == k)
                            {
                                break;
                            }
                            ++pE;
                        }

                        if(pE == a_Entries + sz_EntryCount)
                        {
                            return false;
                        }

                        //We don't need to swap if the entry is the only one in the bucket or if it is the last one.
                        entry* pLast = a_Entries + sz_EntryCount - 1;
                        if((sz_EntryCount > 1) && (pE != pLast))
                        {
                            pE = pLast;
                        }

                        a_Entries = static_cast<entry*>(realloc(a_Entries, sizeof(entry)*(--sz_EntryCount)));
                        if(a_Entries == NULL && sz_EntryCount > 0)
                        {
                            throw std::bad_alloc();
                        }

                        return true;
                    }

                    inline entry* find(const KEY& k) throw()
                    {
                        //Better implement a search policy.
                        entry* pE = a_Entries;
                        while(pE != a_Entries + sz_EntryCount)
                        {
                            if(pE->_k == k)
                            {
                                break;
                            }
                            ++pE;
                        }

                        if(pE == a_Entries + sz_EntryCount)
                        {
                            return NULL;
                        }

                        return pE;
                    }
                };

                HASH_FUNCTION& _hf;
                KEY_COMP _kc;

                size_t sz_TableSize;
                double d_MultFactor;                                            //Recalculate this as 1/sz_TableSize everytime sz_TableSize changes.
                size_t sz_NextResizeLimit;
                size_t sz_EntryCount;
                double d_ExpectedLoadFactor;
                double d_CurrentLoadFactor;

                //If the load factor is relatively high (say >0.5 assuming sizeof(entry) == 2*sizeof(size_t)), it is more space efficient to keep a straight bucket array. But if the load factor is low, memory consumption would be lower if a pointer array of Entries is used here. But, because we would not be much concerned with a little additional memory being used when there are few entries, I think array of bucket objects is better. Further, it bypasses a pointer lookup. May have to reconsider is a situation where multiple hash tables are used (Perhaps as an array).
                bucket* a_Buckets;


                hash_container(const hash_container&);
                hash_container& operator=(const hash_container&);

                inline void calculateMultFactor() throw()
                {
                    d_MultFactor = 1.0f/static_cast<double>(sz_TableSize + 1);
                    //sz_NextResizeLimit = static_cast<size_t>(d_ExpectedLoadFactor*sz_TableSize);
                    //Have a look at this.
                    //TODO
                }

                void resize(size_t szNewSize) throw(std::bad_alloc)
                {
                    if(szNewSize == 0)
                    {
                        szNewSize = 1;
                    }
                    size_t szOldSize = sz_TableSize;
                    for(size_t szI = szNewSize; szI < szOldSize; ++szI)
                    {
                        a_Buckets[szI].~bucket();
                    }

                    a_Buckets = static_cast<bucket*>(realloc(a_Buckets, sizeof(bucket)*szNewSize));
                    if(a_Buckets == NULL)
                    {
                        throw std::bad_alloc();
                    }
                    //Unnecessary at the moment. But, just in case that bucket changes.
                    for(size_t szI = szOldSize; szI < szNewSize; ++szI)
                    {
                         new (&a_Buckets[szI]) bucket();
                    }

                    sz_TableSize = szNewSize;
                    calculateMultFactor();
                }

                inline bucket* get_bucket(const KEY& k) throw()
                {
                    return a_Buckets + _hf(k, sz_TableSize);
                }

                inline bool need_resizing() const throw()
                {

                }
            public:
                //typedef iterator void*;
                //typedef const_iterator void*;

                //iterator Insert(KEY& k, VALUE& v);
                //VALUE& Find(Key& k);
                //const VALUE& Find(Key& k);
                //iterator Find(KEY k);
                //const_iterator Find(KEY k);
                //void Delete(KEY& k);
                //void Delete(iterator it);
                //void Delete(const_iterator it);
                class iterator
                {
                    private:
                        entry* p_Entry;
                        bucket* p_Bucket;

                        friend class bucket;

                    public:
                        iterator(entry* pEntry)
                            :p_Entry(pEntry)
                        {
                        }

                        iterator()
                        {
                            p_Entry = NULL;
                        }

                        iterator(const iterator& it)
                        {
                            this->p_Entry = it.p_Entry;
                        }

                        inline VALUE& operator*() const
                        {
                            return p_Entry->_v;
                        }

                        inline bool operator==(const iterator& it) const
                        {
                            return this->p_Entry == it.p_Entry;
                        }

                        inline bool operator!=(const iterator& it) const
                        {
                            return !(*this == it);
                        }

                        inline iterator& operator=(const iterator& it)
                        {
                            this->p_Entry = it.p_Entry;
                        }

                        inline VALUE* operator->() const
                        {
                            return &p_Entry->_v;
                        }

                        inline iterator operator++()
                        {
                            return *this;
                        }

                        inline iterator& operator++(int)
                        {
                            //WRONG!!!
                            //TODO : Change this.
                            return *this;
                        }
                };

            private:
                iterator _EndIt;

            public:
                hash_container(HASH_FUNCTION& hf, size_t szTableSize = 1024, double dLoadFactor = 0.7f, KEY_COMP kc = KEY_COMP())throw(std::bad_alloc)
                    :_hf(hf), sz_TableSize(szTableSize), d_ExpectedLoadFactor(dLoadFactor), _kc(kc)
                {
                    if(d_ExpectedLoadFactor < 0.1f)
                    {
                        d_ExpectedLoadFactor = 0.1f;
                    }

                    a_Buckets = NULL;
                    sz_TableSize = 0;
                    if(szTableSize == 0)
                    {
                        szTableSize = 1;
                    }
                    resize(szTableSize);
                    d_CurrentLoadFactor = 0.0f;
                    sz_EntryCount = 0;

                    _EndIt = iterator(NULL);
                }

                virtual ~hash_container()
                {
                    for(size_t szI = 0; szI < sz_TableSize; ++szI)
                    {
                        a_Buckets[szI].~bucket();
                    }
                }

                inline iterator find(const KEY& k) throw()
                {
                    bucket* pBucket = get_bucket(k);
                    return pBucket->find(k);
                }

                inline bool insert(const KEY& k, const VALUE& v) throw(std::bad_alloc)
                {
                    bucket* pBucket = get_bucket(k);
                    bool bRet = false;
                    try
                    {
                        bRet = pBucket->insert(k, v);
                    }
                    catch(std::bad_alloc& e)
                    {
                        //What now?
                        throw e;
                    }
                    if(bRet == true)
                    {
                        ++sz_EntryCount;
                    }
                    return bRet;
                }

                inline VALUE& operator[](const KEY& k) throw(std::bad_alloc)
                {
                    bucket* pBucket = get_bucket(k);

                }

                inline bool erase(const KEY& k) throw(std::bad_alloc)
                {
                    bucket* pBucket =  get_bucket(k);
                    bool bRet = false;
                    try
                    {
                        bRet = pBucket->erase(k);
                    }
                    catch(std::bad_alloc& e)
                    {
                        throw e;
                    }
                    if(bRet == true)
                    {
                        --sz_EntryCount;
                    }
                    return bRet;
                }

                inline iterator end() const
                {
                    return _EndIt;
                }

                inline size_t size() const
                {
                    return sz_EntryCount;
                }

                inline size_t table_size() const
                {
                    return sz_TableSize;
                }

                inline double current_load_factor() const
                {
                    return d_MultFactor*static_cast<double>(sz_EntryCount);
                }

                inline double expected_load_factor() const
                {
                    return d_ExpectedLoadFactor;
                }
        };
}

#endif

【问题讨论】:

  • 这看起来像家庭作业。请修改您的问题标题以包含“作业”一词。
  • 对我来说看起来不像家庭作业。看起来他很好奇如何实现迭代器。
  • @Alf P. Steinbach:不是作业。我正在为哈希表实现迭代器。
  • 对我来说好像是作业。它从错误的一端开始。例如,要询问operator-&gt; 是如何过载的,需要知道operator-&gt; 是否过载。如果不知道该问题的答案,就无法知道这一点。所以,功课。对不起,@nakiya。
  • @Alf P. Steinbach:什么?这听起来很做作。也许你想要代码? :p。等我发帖。

标签: c++ operator-overloading


【解决方案1】:

.1。 operator-&gt; 应该几乎总是返回一个指针类型。当使用value_typeT 充当迭代器时,它应该返回T*

在一些罕见的情况下,operator-&gt; 可能会返回一个不同的类类型,它也有一个operator-&gt; 成员函数。

.2。对于operator++ 的任何一种形式必须返回什么没有技术要求,但通常的约定使它们的行为最像内置含义。

class T {
public:
    // pre-increment
    T& operator++() { increment_me(); return *this; }
    // post-increment
    T operator++(int) { T copy(*this); increment_me(); return copy; }
    //...
};

预增表达式++x的内置含义是先将数字递增,然后将左值返回给递增的数字。 T&amp; 的返回类型作用类似。

后增量表达式“x++”的内置含义使变量递增,但返回变量先前值的右值副本。因此,大多数用户定义的重载都会返回原始值的副本(实际上永远不会是引用)。

【讨论】:

  • @Zooba 提出了同样的问题:我对 operator-> 感到困惑。如果它返回类 T*,这是否意味着用法应该是这样的:collection&lt;T&gt;::iterator it; it.operator-&gt;()-&gt;member = x;?还是我弄错了?
  • @nakiya: 每个重载运算符(newdelete 及其数组亲属除外)可以使用运算符本身以短格式调用,也可以使用operator 关键字以长格式调用.例如。 operator+(x,y) 长于 x+yit.operator-&gt;()-&gt;member 是使用operator-&gt; 长格式的正确方法,it-&gt;member 是使用operator-&gt; 短格式的正确方法。
【解决方案2】:

对于一个迭代器类型,operator->是如何重载的?

不是。 operator-> 只能在类类型上重载。

如果你的意思是"How do I overload it to return an integer type".
那么答案是你不能。 operator-> 的结果本身被取消引用,因此必须是指针类型(或对象(引用),它是具有重载 operator->() 的类类型)。

假设它是 T 类对象集合的迭代器,它应该返回什么值?

它会返回一个指向T的指针

struct Y { int a; };
std::vector<Y> plop(/* DATA TO INIT*/);

std::vector<Y>::iterator b = plop.begin();
b->a = 5; // here b.operator->() returns a pointer to Y object.
          // This is then used to access the element `a` of the Y object.

为什么 operator++() 由 T& 类返回,而 operator++(int) 由 T 类返回?

从技术上讲,他们可以退回任何东西。但通常它们会按照您的建议实施。
这是因为这些方法的标准实现:

class X
{
     public:
         // Simple one first. The pre-increment just increments the objects state.
         // It returns a reference to itself to be used in the expression.
         X& operator++()
         {
              /* Increment this object */
              return *this;
         }

         // Post Increment: This has to increment the current object.
         // But the value returned must have the value of the original object.
         //
         // The easy way to do this is to make a copy (that you return). The copy
         // has the original value but now is distinct from this. You can now use
         // pre-increment to increment this object and return the copy. Because
         // the copy was created locally you can not return by reference.
         X operator++(int)
         {
             X  copy(*this);
             ++(*this);
             return copy;
         }
};

我知道这两个代表前缀增量和后缀增量。但是为什么返回值不同呢?

见上面代码中的 cmets。

【讨论】:

    【解决方案3】:
    1. operator-&gt; 应该返回一个指向T 类型的指针(即T*)。

    2. 后缀增量必须返回该值的副本,因为它在使用该值之前执行增量。前缀增量可以简单地在增量后返回*this

    简单的实现可能如下所示:

    T T::operator++(int)
    {
        T temp = *this;
        ++*this;
        return temp;
    }
    
    T& T::operator++()
    {
        this->value += 1;
        return *this;
    }
    

    【讨论】:

    • 我对@9​​87654326@ 感到困惑。如果它返回class T*,是不是意味着用法应该是这样的:collection&lt;T&gt;::iterator it; it.operator-&gt;()-&gt;member = x;?还是我弄错了?
    • 不要显式调用操作符。只需写it-&gt;member = x。编译器会自动在it 上生成对operator-&gt; 的调用以获取T*,然后将内置的-&gt; 逻辑应用于此。
    • @Karl Knechtel:我猜这就是答案。我想知道那个额外的-&gt; 消失在哪里了。
    • @nakiya 返回T*的唯一选择是T&amp;,并且在重载-&gt;运算符之后添加了引用(这也是this不是引用的原因) .编译器将a-&gt;b 重写为(*T::operator-&gt;(a)).b,这要求返回值是一个指针。如果 C++ 被“重新启动”,它可能会返回一个引用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多