【问题标题】:How to prevent value assignment with inherited operator[]?如何防止使用继承的运算符 [] 进行赋值?
【发布时间】:2018-10-07 12:06:01
【问题描述】:

我有一个名为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)
}

测试.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

这个操作可以吗?

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

【问题讨论】:

  • 返回一个常量引用?
  • @Vivick - 我正在考虑它,但我不能,因为运算符是从其父常规 ArrayList 继承和重载的(它可以分配)。
  • @t4dohx 编辑问题并添加您的限制,这样您就不会得到不适当的答案。
  • ArrayList的部分合约支持使用operator []修改数据。如果你阻止了这一点,你就违反了 Liskov 替换原则。虽然可以在语法上执行此操作,但您不想破坏 ArrayList 的合同。

标签: c++ list inheritance container-data-type


【解决方案1】:

比继承更喜欢聚合

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 正确的points out,当您使用继承时,您保证您的类SortedArrayList 可以在所有可能的情况下充当ArrayList。在您的示例中显然不是这种情况。

您可以阅读更多here,了解如何违反 Liskov 的替代原则是一个坏主意。

【讨论】:

  • 很好的解决方案,但有一个小缺点:您不能以这种方式将 ArrayList 和 SortedArrayList 加入同一个容器中。两者的通用基类将允许这样做。还要注意:接口必须重复。嗯,可以同时是优势和劣势...
  • @Aconcagua 感谢您的评论!您能否详细说明“在同一个容器中加入 ArrayList 和 SortedArrayList”?另外,请注意我不考虑这种重复:SortedArrayList 不是ArrayList,就像std::unordered_set 不是std::set,尽管两个模板具有相似的接口。
  • 他是说他们不再共享一个基类,所以你不能创建一个std::vector&lt;std::unique_ptr&lt;ArrayList&gt;&gt; 并将ArrayListSortedArrayList 填入其中..
  • 啊,谢谢你的澄清。对于本质上不同的对象,我更喜欢使用std::anystd::variant 的容器,而不是尝试将它们放在类层次结构中。我同意这不像简单的vector&lt;unique_ptr&lt;T&gt;&gt; 那样友好,但也没有那么糟糕。
【解决方案2】:

你不应该这样做。它表示设计不当 请参阅the C++ FAQ on Inheritance。如果您的子类不能以所有方式用作基类 (LSP),则它不满足公共继承的“is-a”要求。

如果你想拥有一种允许成员替换的容器和另一种不允许成员替换的容器,那么定义只允许 const 成员访问的基类(无需将其设为虚拟)。然后从那里分支到MutableList和ImmutableList,让SortedArrayList派生自Immutable列表。

【讨论】:

  • 非镜像链接:isocpp.org/wiki/faq/proper-inheritance 也建议将 LSP 扩展为 Liskov 替换原则,以使该链接更加明显。
  • 谢谢。我工作中的防火墙阻止了大多数有用的网站。我会更新链接。
  • 这个解释真棒。非常感谢。
【解决方案3】:

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

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

【讨论】:

  • 感谢您的回答。我已经有 T& get(int index) 方法(LHS),操作员只是调用 get 方法。我发布运营商只是为了更清楚我除了什么。
【解决方案4】:
  1. 您为什么将索引作为参考传递?完全不需要……
  2. 我个人建议对数组索引使用无符号整数类型(无论如何,负索引的含义是什么???)。
  3. 由 value 返回的类型的 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 引用...如果用户可以使用引用,很好,否则他/她无论如何都会复制该值...

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

由于涉及到继承,事情变得更加复杂。您不能仅仅摆脱某些继承的函数,而继承的函数允许元素修改。

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

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...
}

插入函数可能是基类中的纯虚函数(其中一个通用接口似乎很合适)或仅在派生类中可用。决定你...

【讨论】:

  • 感谢您的回答,但这应该有什么帮助?您提到的所有 3 点都与我的问题无关。请参阅修改后的问题。
  • 这本来是对最初发布的问题的正确答案。
  • 禁止访问虚拟方法违反了 LSP
  • 此外,这可能会触发编译器警告(另一个迹象表明这是一个坏主意)
  • 我写问题的时候,根本没有涉及到继承……修改了一个小改编。尽管如此,我还是全职工作,我根本负担不起每半分钟检查一次问题的变化,所以很抱歉反应时间不长,请对我有点耐心......
猜你喜欢
  • 2012-02-28
  • 2021-08-28
  • 2021-06-07
  • 2015-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-15
相关资源
最近更新 更多