如何防止用继承的运算符[]赋值?

How to prevent value assignment with inherited operator[]?

我有一个名为 SortedArrayList<T> 的自定义结构,它根据比较器对其元素进行排序,我想防止使用 operator[] 进行分配。

示例:

ArrayList.h

template <typename T> class ArrayList : public List<T> {
    virtual T& operator[](const int& index) override; //override List<T>
    virtual const T operator[](const int& index) const override; //override List<T>
}

SortedLinkedList.h 与以下运算符

template <typename T> class SortedArrayList : public ArrayList<T> {
   public:

   SortedArrayList<T>(const std::function<bool(const T&, const T&)>& comparator);

   T& operator[](const int& index) override; //get reference (LHS)
   const T operator[](const int& index) const override; //get copy (RHS)
}

Test.h

ArrayList<int>* regular = new ArrayList<int>();
ArrayList<int>* sorted = new SortedArrayList<int>(cmpfn);

(*regular)[0] == 5; //allow
(*regular)[0] = 5;  //allow
(*sorted)[0] == 7; //allow
(*sorted)[0] = 7; //except

这个操作可以吗?

我所说的阻止是指抛出异常或警告用户不要这样做的东西。

在我看来,这里的最佳实践是实现 at(const int& index) 方法而不是重载 []。无论如何,这对界面的用户来说会更清楚。

std::map和其他std数据结构中也有类似的功能。例如:http://www.cplusplus.com/reference/map/map/at/

  1. 为什么要传递索引作为参考?绝对不需要...
  2. 我个人建议对数组索引使用无符号整数类型(负索引到底是什么意思???)。
  3. const 由值返回的类型(几乎)没有意义 - 它无论如何都会被复制到另一个变量(然后 是可修改的),但是你阻止了移动语义。 ..

所以:

T& operator[](unsigned int index); //get reference (LHS)
T operator[](unsigned int index) const; //get copy (RHS)

(只是一些改进建议...)

现在进入实际问题:禁止修改非常简单:

//T& operator[](unsigned int index); //get reference (LHS)
T const& operator[](unsigned int index) const; //get copy (RHS)

只有一个索引运算符,总是返回 const 引用...如果用户可以接受引用,那很好,否则 he/she 无论如何都会复制值...

根据修改后的问题进行编辑:

现在涉及到继承,事情变得更加复杂。你不能只去掉一些继承的函数,继承的函数允许元素修改。

在给定的情况下,我会考虑重新设计(如果可能的话):

class ArrayListBase
{
public:
    T const& operator[](unsigned int index) const;
    // either copy or const reference, whichever appears more appropriate to you...
};

class ArrayList : public ArrayListBase
{
public:
    using ArrayListBase::operator[];
    T& operator[](unsigned int index);
}


class SortedArrayList : public ArrayListBase
{
public:
    // well, simply does not add an overload...
}

插入函数在基础 class 中可能是纯虚拟的(公共接口似乎适合),或者仅在派生的 class 中可用。决定你...

聚合优于继承:

template <typename T> class SortedArrayList {
   ArrayList<T> m_the_list;
   public:

   SortedArrayList<T>(const std::function<bool(const T&, const T&)>& comparator);

   const T& operator[](const int& index) const {return m_the_list[index];}; // always get const reference

   // Can act as a *const* ArrayList<T>, but not as a mutable ArrayList<T>, as that would violate Liskov's substitution principle.
   operator const ArrayList<T>&() const {return m_the_list;}
}

Stephen Newell correctly 一样,当您使用继承时,您可以保证您的 class SortedArrayList 在任何可能的情况下都可以充当 ArrayList。在您的示例中显然不是这种情况。

您可以阅读更多内容here,了解违反 Liskov 替换原则是多么糟糕的想法。

你不应该这样做。这表明设计不当见 the C++ FAQ on Inheritance. Your subclass doesn't fulfill the "is-a" requirement for public inheritance if it can't be used in all ways as the base class (LSP)。

如果您想要一种类型的容器允许成员替换而另一种不允许,则定义仅允许 const 成员访问的基础 class(无需将其设为虚拟)。然后从那里分支到 MutableList 和 ImmutableList,并让 SortedArrayList 从 Immutable 列表派生。