作为C ++的新手,我很难理解我遇到的链接问题。以下是让我头疼的文件:

#pragma once

#include <string>
#include "finite_automaton.h"


template<typename RL>
class RegularLanguage {
public:
    bool empty();
    RegularLanguage<RL> minimize();
    RegularLanguage<RL> complement();
    RegularLanguage<RL> intersectionWith(RegularLanguage<RL> other);
    RegularLanguage<RL> unionWith(RegularLanguage<RL> other);
    RegularLanguage<RL> concatenate(RegularLanguage<RL> other);
    RegularLanguage<RL> kleeneStar();

    /*
     * Returns a regular expression that describes the language of this automaton.
     */
    std::string string();

    bool disjoint(RegularLanguage<RL> other) {
        return intersectionWith(other).empty();
    }

    bool containedIn(RegularLanguage<RL> super) {
        return intersectionWith(super.complement()).empty();
    }

    bool contains(RegularLanguage<RL> sub) {
        return complement().intersectionWith(sub).empty();
    }

    bool equals(RegularLanguage<RL> other) {
        return contains(other) && containedIn(other);
    }
};


编译项目时,在链接阶段会出现以下错误:

undefined reference to `RegularLanguage<FiniteAutomaton>::complement()'
undefined reference to `RegularLanguage<FiniteAutomaton>::intersectionWith(RegularLanguage<FiniteAutomaton>)'
undefined reference to `RegularLanguage<FiniteAutomaton>::empty()'


RegularLanguage<RL>::containedIn(..)RegularLanguage<RL>::contains(..)都适用。

我究竟做错了什么?我确实得到了与实现此模板类的类有关的一些相关错误,但为了避免不必要地发布过多的代码,我将它们省略了。

最佳答案

因此,总结一下您要做什么,您有一个类模板:

template<typename RL> class RegularLanguage {...};


某些方法已实现,而其他方法仅声明但未实现。然后尝试从中派生,并在派生类中实现那些在RegularLanguage中未实现的其他方法:

class FiniteAutomaton : public RegularLanguage<FiniteAutomaton>


您似乎正在混淆C ++中的两个概念:继承(具体来说是多态)和模板。

您的假设是,通过从RegularLanguage继承,您可以在派生类中实现缺少的东西。那不是模板的工作方式,但是多态确实可以这样工作。您需要:


在类模板RegularLanguage中完全实现所有方法,然后从中派生FiniteAutomaton
使用(可能是纯净的)RegularLanguage方法将virtual作为抽象基类(而不是类模板),然后从中派生。您想要在FiniteAutomaton中实现的方法可以。这些可能是纯粹的,也可能不是。您不想在基类中实现的方法应声明为纯(=0),然后在派生类中实现。


在我看来,使用#2可以更好地完成您真正想做的事情,因此,下面是一个不完整的示例:

class RegularLanguage {
public:
    virtual RegularLanguage* Clone() = 0;  // Creates a copy of this object
    virtual RegularLanguage& complement() = 0; // pure virtual must be implemented in derived class
    virtual bool containedIn(RegularLanguage& super) // non-pure, does not have to be implemented in derived
    {
        return intersectionWith(super.complement()).empty();
    }
  virtual ~RegularLanguage() {}
};

class FiniteAutomaton
:
  public RegularLanguage
{
public:
  RegularLanguage* Clone()
  {
    RegularLanguage* clone = new FiniteAutomaton (*this);
    return clone;
  }

  RegularLanguage* complement()
  {
    RegularLanguage* comp = this->Clone();
    comp->SetSomeStuff();
    return comp;
  }
};


我上面没有提到很多隐藏的细节。例如,complement的返回类型现在是RegularLanguage指针,而不是RegularLanguage值。这是必需的1,以避免所谓的Slicing Problem破坏多态性。

另外,我在这里放弃了模板的使用,因为当我实现了模板之后,显然并不需要模板。但是,模板的使用和多态性并不完全相互排斥。基类可以使用模板,实际上基类可以是类模板,而仍然是抽象基类。但是派生类必须从该基类模板的具体实例派生。这里的事情变得有些复杂。请注意,不要将所有东西都看成是钉子,并被那把锤子带走。

另外,我之前没有在基类中实现虚拟析构函数(现在已解决),但是您必须2。请记住这一点:如果要派生一个类,则几乎在每种情况下都应具有一个虚拟析构函数。

另外,我在基类中添加了一个Clone方法作为纯virtual。这是对以下建议的回应:complement()不应返回该对象的实例,而应返回一个新实例,该实例是该实例的补充。在多态层次结构中,当我们复制对象的副本时,几乎总是需要通过基类指针进行复制,因此在这种设计中通常存在Clone()类型的方法。



1“这是必需的[通过指针传递]:实际上,您也可以返回一个引用。如果可以,请返回一个引用,但是这里我们需要返回一个指针。(实际上,我们应该返回智能指针,但这是一个切线)

2“您必须[实现虚拟析构函数]:从技术上讲,如果您打算通过基类指针delete该对象,则需要具有虚拟析构函数。在我的职业生涯中,我从未见过这样的实例:不应在多态层次结构的基类中实现虚拟析构函数,即使您没有通过基类指针delete的计划,也应始终这样做。

关于c++ - 类内的C++链接,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21342008/

10-10 15:03