类中的C ++链接

时间:2014-01-24 20:34:55

标签: c++ linker

对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(..)

我做错了什么?我确实得到了一些与实现这个模板类的类相关的错误,但是我把它们留了下来,以免发布不必要的代码。

3 个答案:

答案 0 :(得分:3)

因此,总结一下您要做的事情,您有一个类模板:

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

实现了一些方法,其他方法只是声明,但没有实现。然后,您尝试从此派生,并在派生类中实现RegularLanguage中未实现的其他方法:

class FiniteAutomaton : public RegularLanguage<FiniteAutomaton>

您似乎在C ++中混淆了两个概念:继承(特别是多态)和模板。

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

  1. 完全实现类模板RegularLanguage中的所有方法,然后从中导出FiniteAutomaton
  2. 使用(可能是纯粹的)RegularLanguage方法使virtual成为抽象基类 - 而不是类模板,并从中派生出来。您希望在FiniteAutomaton中实施的方法可以是。那些可能是也可能不是纯粹的。您 希望在基类中实现的方法应声明为纯(=0),然后在派生类中实现。
  3. 听起来我觉得你真正想做的事情会更好地完成#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的(多态!)引用 - 甚至不必知道在那个中隐藏了什么类型的解析器。