对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(..)
。
我做错了什么?我确实得到了一些与实现这个模板类的类相关的错误,但是我把它们留了下来,以免发布不必要的代码。
答案 0 :(得分:3)
因此,总结一下您要做的事情,您有一个类模板:
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
的计划。
答案 1 :(得分:1)
您需要将bool empty()
(以及其他未在基础中实现的)声明为基类中的纯虚拟:
virtual bool empty() = 0;
然后在这样的后代类中覆盖/实现它们:
virtual bool empty(){
...
}
答案 2 :(得分:1)
此代码存在一系列问题。最明显的一点是:你需要你的函数纯虚拟。但那只是一个开始。
当您将函数设置为纯虚拟时,您会立即注意到代码不再编译。这些工具会告诉您无法实例化抽象类。原因是您通过值从函数返回ReguarLanguage<...>
类型的对象,从而调用对象切片的诅咒(查找它)。无法实例化抽象类只是对象切片的表现形式。
为了消除可怕的诅咒,你必须通过指针进行Rite of Return,从而调用手动记忆管理的较小诅咒(这些天相对容易被一点智能指针魔法击败)。
但是你不仅要归还语言,还要接受它们作为参数。在这里,您需要一个简单的“按引用传递”的咒语,它不需要任何手动内存管理。在您的情况下,您可以通过仅使用“other”参数的公共接口来实现交集和所有其他事情,因此这个简单的更改就足够了。
哦,不要忘记虚拟析构函数。永远不要忘记虚拟析构函数......如果你知道你不需要它,那就省略它,如果你的类适合OO范式,那就永远不会。
如果查看RegularLanguage类模板,您可能会注意到它从不使用其模板参数。如果是这种情况,可以安全地去模板化。不,等等,它确实使用了参数 - 它如何创建,例如补充?
简单地说,如果你有一个解析一种语言的有限自动机,那么补语的解析器就不必是一个有限的自动机。它可能包含或引用 FA(或其他东西!)。所以你可能想要一个名为Complement的RegularLanguage的子类,它将包含对另一个RegularLanguage的(多态!)引用 - 甚至不必知道在那个中隐藏了什么类型的解析器。