使用子类操作类

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用子类操作类相关的知识,希望对你有一定的参考价值。

在这个模板问题中,有一个名为List<T>的用户链接列表类没有删除其最后一个元素的方法,我们假设我们无法直接更改它。

template<typename T>
class List {
public:
    class Iterator {
    public:
        T get_data() { 
            T to_be_returned = current->data;
            return to_be_returned;
        }
        void next_element() {
            current = current->next;
        }
        bool has_more_elements() {
            return current != NULL;
        }
    private:
       //...
    };
public:
    List();
    ~List();
    void print();
    void push_front(T x);
    void push_back(T x);
    Iterator get_iterator(); 
private:
   //...
};

这个问题的第一部分说创建MyList List的子类并在那里实现remove_last()。我已经在下面编写了代码作为解决方案,但由于这是一个模板问题,我在这段代码中遗漏了一些重要的模板吗?

template<typename T>
class MyList : public List<T>
{
public:
    void remove_last(List<T>& l)
    {
        typename MyList<T>::Iterator it = l.get_iterator();
        typename MyList<T>::Iterator it2 = it;

        if(!it.has_more_elements())
            return;

        this->push_back(it.get_data());
        it2.next_element();
        it.next_element();
        if(it.has_more_elements())
            it.next_element();
        else
            return MyList<T>::~MyList();

        while(it.has_more_elements())
        {
            this->push_back(it2.get_data());
            it2.next_element();
            it.next_element();
        }
    }
};

问题的下一部分说假设我们只需要删除最后一个元素,如果它是一个带小写第一个字符的单词,是否可以用模板实现它?如果没有,我们怎么解决呢?

答案

您的代码中存在一些问题。

首先,如果要继承List<T>,它需要有

  1. 要么是virtual公共析构者,要么
  2. 一个非virtual保护的析构函数。

否则,任何引用MyList<T>对象的用户代码,只要它们调用相应对象的析构函数,就会有List<T>的指针/引用。

如果您不允许触摸提供的List<T>代码,您可以通过protectedprivate机制继承它,以确保指向List<T>的指针不会导致任何麻烦。但是,您需要记住,当您使用List<T>List<T>*时,您将无法使用List<T>&提供的公共API。

然后,你有另一个问题。在你的问题中,你提到你需要实现remove_last()。但是,您在MyList<T>::remove_last(List<T>& l)的签名与要求不符。我认为,您应该更改签名以满足此要求。

我在你的代码摘录中看到的另一个问题是你明确地调用了析构函数MyList<T>::~MyList()。只要在相应的行上满足else条件,这将在您的应用程序中具有未定义的行为。考虑一下这个案子

/* some code */
{
  MyList<double> mylist;
  /* fill the list, do something with it */
  mylist.remove_last(); /* assuming you have changed the signature */
  /* assume you have hit the else statement as mentioned above */
  /* when leaving the scope, mylist will be destructed */
  /* double destruction problem */
}
/* some code */

请参阅cppreference中提供的评论

请注意,直接为普通对象(例如局部变量)调用析构函数会在范围结束时再次调用析构函数时调用未定义的行为。

最后,为了回答你的问题,你应该考虑写一些东西:

/* private inheritance to avoid possible leaks */
/* this means that List<T> is your implementation detail */
/* Then, you need to reveal the API of List<T> properly */
template <typename T> class MyList : private List<T> {
public:
  void remove_last() {
    auto it = List<T>::get_iterator();

    /* reach the last element by iterating through the list */
    while (it.has_more_elements())
      it.next_element();

    /* do something with the iterator, as you know that it has the last element
     * now */
  }
};

在这种情况下,您提供的代码不足以给出具体答案,因为显然,push_back的迭代器API只提供push_frontList<T>功能。您需要在List<T>或其迭代器上使用一些公共功能才能修改基础数据。此外,请记住,在我的解决方案建议中it不是实际的数据点;它是用于过去的迭代器的哨兵。

如果您确定没有其他可用的公共功能,您可以创建一个List<T>并迭代您的变量,同时push_back(...)ing值,直到您到达最后一个元素,如:

template <typename T> class MyList : private List<T> {
public:
  void remove_last() {
    List<T> temp;

    auto current = List<T>::get_iterator();
    auto prev{current};

    while (current.has_more_elements()) {
      prev = current;
      current.next_element();
      if (current.has_more_elements())
        temp.push_back(prev.get_data());
    }

    List<T>::operator=(std::move(temp)); /* call copy/move assignment */
  }
};

最后评论:

  1. 我很难检查我的解决方案是否实际按预期工作,因为你没有提供minimal working example
  2. 请不要使用NULL,因为在这种情况下,它最有可能是指nullptr,这是更好的选择。

编辑。在这个问题中,我认为模板可以派上用场的唯一地方是用户应用一些谓词函数。考虑以下修改,例如:

template <typename T> class MyList : private List<T> {
public:
  template <class Predicate> void remove_last(Predicate &&pred) {
    List<T> temp;

    auto current = List<T>::get_iterator();
    auto prev{current};

    while (current.has_more_elements()) {
      prev = current;
      current.next_element();
      if (current.has_more_elements()) {
        auto value = prev.get_data();
        if (pred(value))
          temp.push_back(std::move(value));
      }
    }

    List<T>::operator=(std::move(temp)); /* call copy/move assignment */
  }
  void remove_last() {
    remove_last([](const T &) { return true; });
  }
  void remove_last_if_word_lowercase() {
    remove_last([](const T &) {
      /* apply the *only if word and starts with lowercase logic */
      // return ...;
    });
  }
};

在上面,您可以避免谓词的代码重复,并允许使用模板化谓词仿函数进行编译器端优化。

以上是关于使用子类操作类的主要内容,如果未能解决你的问题,请参考以下文章

设计问题:仅对超类进行操作的子类应用特定处理

使用java实现面向对象 第三章

在“设置”片段中夸大类PreferenceScreen的错误

使用子类操作类

模板方法模式

在Android中,如何将数据从类传递到相应的布局/片段文件?