3

在下面的一些向量实现中,如何避免数组订阅运算符中的悬空引用?如果realloc更改指针,则先前从 operator[] 获得的引用不再有效。我不能为此使用 new/delete。我必须使用malloc/ realloc/ free

template <class T>
class Vector
{
  public:
  typedef size_t size_type_;

  ...

  T& operator[] (const size_type_);
  void push_back (const T&);

  ...

  private:
  const size_type_ page_size_;
  size_type_ size_;
  size_type_ capacity_;
  T* buffer_;
};


template<class T>
inline
T&
some_namespace::Vector<T>::operator[] (const size_type_ index)
{
  ASSERT(index < size_);
  return buffer_[index];
}


template<class T>
inline
void
some_namespace::Vector<T>::push_back(const T& val)
{
  if (size_ >= capacity_)
  {
    capacity_ += page_size_;
    T* temp = (T*)(realloc(buffer_, capacity_*sizeof(T)));

    if (temp != NULL)
    {
      buffer_ = temp;
    }
    else
    {
      free(buffer_);
      throw some_namespace::UnexpectedEvent();
    }
  }

  buffer_[size_++] = val;
}

顺便说一句,代码中悬空引用的来源是这样的:

v_.push_back(v_[id]);

其中 v_ 是 Vector 的一个实例。为了防止这种情况,新的 push_back 是:

template<class T>
inline
void
some_namespace::Vector<T>::push_back(const T& val)
{
  if (size_ >= capacity_)
  {
    const T val_temp = val; // copy val since it may come from the buffer_
    capacity_ += page_size_;
    T* temp = (T*)(realloc(buffer_, capacity_*sizeof(T)));

    if (temp != NULL)
    {
      buffer_ = temp;
    }
    else
    {
      free(buffer_);
      throw some_namespace::UnexpectedEvent();
    }

    buffer_[size_++] = val_temp;
  }
  else
  {
    buffer_[size_++] = val;
  }
}
4

1 回答 1

4

基本上你可以做三件事:

  • 接受它并记录下来。这就是 STL 所做的,它实际上是相当合理的。
  • 不要返回参考,返回副本。当然,这意味着您不能再分配给向量的元素(v[42] = "Hello")。在这种情况下,您需要一个operator[]标记为const.
  • 返回一个代理对象,它存储对向量本身和索引的引用。您向代理添加分配以const T&允许写入向量的元素,并且您需要提供某种方式来访问/读取该值,很可能是一个隐含的operator const T&. 由于代理对象在每次访问时向向量询问元素的当前位置,因此即使向量在调用之间更改了这些元素的位置,这仍然有效。只是要小心多线程。

这是代理的草图,未经测试且不完整:

template<typename T> class Vector
{
private:
    // ...

    // internal access to the elements
    T& ref( const size_type_ i );
    const T& ref( const size_type_ i ) const;

    class Proxy
    {
    private:
        // store a reference to a vector and the index
        Vector& v_;
        size_type_ i_;

        // only the vector (our friend) is allowed to create a Proxy
        Proxy( Vector& v, size_type_ i ) : v_(v), i_(i) {}
        friend class Vector;

    public:
        // the user that receives a Proxy can write/assign values to it...
        Proxy& operator=( const T& v ) { v_.ref(i_) = v; return *this; }

        // ...or the Proxy can convert itself in a value for reading
        operator const T&() const { return v_.ref(i_); }
    };

    // the Proxy needs access to the above internal element accessors
    friend class Proxy;

public:
    // and now we return a Proxy for v[i]
    Proxy operator[]( const size_type_ i )
    {
        return Proxy( *this, i );
    }
};

请注意,以上内容是不完整的,整个技术有一些缺点。最重要的问题是 API 中的代理“泄漏”,因此无法满足某些用例。您需要使该技术适应您的环境,看看它是否适合。

于 2013-09-24T06:44:26.967 回答