3

最近我尝试解决 C++ 继承和多态性,但我遇到了一些对我来说毫无意义的问题。我在单独的文件中有 2 个标头和一个带有实现的 cpp 文件。我的代码的简短摘要如下:

#ifndef MANDEL_H_
#define MANDEL_H_

class Mandel{

public:
    virtual void compute("various arguments") = 0;

    //dummy destructor, I must have one or compile is sad and I dunno why
    virtual ~Mandel();
private:
    virtual int compute_point("various arguments") = 0;
};

#endif

这是我的“祖父”标题,名为“Mandel.h”。现在转到“父亲”标题。下一个标题指定了一些特定于 Mandel 的白色和黑色实现的变量,称为“Black_White_Mandel.h”:

#ifndef BLACK_WHITE_MANDEL_H_
#define BLACK_WHITE_MANDEL_H_

#include "Mandel.h"

class Black_White_Mandel: public Mandel {

protected:
    int max_iterations; //a specific variable of this Black_White Version
};

#endif

现在遵循 Black_White_Mandel 标头的实现,在一个名为 White_Black_Mandel_Imp1.cpp 的单独文件中:

#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#include "Mandel.h"
#include "Black_White_Mandel.h"

using namespace std;

//constructor
Black_White_Mandel::Black_White_Mandel(){
    max_iterations = 255;
}

//destructor
Black_White_Mandel::~Black_White_Mandel(){}

int Black_White_Mandel::compute_point("various arguments") {
    //code and stuff
    return 0;
}

void Black_White_Mandel::compute("various arguments") {
     //code and stuff
}

因此,Mandel.h 有两个必须实现的功能,因为它们是虚拟的并且“=0”。在 White_Black_Mandel_Imp1.cpp 中,当我实现编译器发疯的那些函数时。它说函数没有在 White_Black_Mandel.h 中定义,虽然这是真的,但它们是在 Mandel.h 中定义的。因此,通过继承,White_Black_Mandel_Imp1.cpp 应该知道它有义务从 Mandel.h 实现这些功能。

我不明白,我的一个朋友说我的 White_Black_Mandel.h 文件应该是 Mandel.h 的精确副本,但还有一些额外的东西,但这对我来说真的很愚蠢,没有任何意义。

我究竟做错了什么?

4

5 回答 5

7

尽管您的祖先类中有 2 个纯虚方法,但这并不意味着它们的原型已准备好在子类中使用。

即使在您的子类中,您也必须声明原型:

class Black_White_Mandel: public Mandel {

public:
    virtual void compute("various arguments")

protected:
    int max_iterations; //a specific variable of this Black_White Version

private:
    virtual int compute_point("various arguments");
};

关键字是可选的virtual,但知道该方法确实是虚拟的很有用。你不必在这个特定的子类中实现它们,你可以避免指定任何东西,但你仍然有两个必须实现的纯虚方法,所以你将无法实例化这个子类的任何对象(你将有无论如何都要在层次结构树中实现它们)。

虚拟析构函数是必需的,否则在类似情况下:

Base *derived = new Derived();
delete derived;

编译器无法调用正确的析构函数。

于 2013-03-15T00:26:10.947 回答
4

compute为和compute_pointto添加原型Black_White_Mandel

在某些情况下,您从具有纯虚函数的基类继承并且不实现所有这些:您的派生类将保持抽象,并且需要从另一个类继承等,直到实现所有纯虚函数。

例如

class A {
    virtual void foo() = 0;
    virtual void bar() = 0;
};

class B : public A {
    virtual void foo() {};
};

class C : public B {
    virtual void bar() {};
};

class D : public A {
    virtual void foo() {};
    virtual void bar() {};
};

上面唯一可实例化的类是CD

于 2013-03-15T00:24:21.063 回答
3

White_Black_Mandel_Imp1.cpp 应该知道它有义务

它没有也不应该。它也可以决定成为一个抽象类,在这种情况下,它可以不理会这些功能。

于 2013-03-15T00:25:58.763 回答
3

必须在实现类中提供声明的原因是派生类使用不同的返回类型覆盖虚函数是合法的,只要新的返回类型与原始返回类型是协变的。例如,您的基类可以返回BaseReturnedObject&,但您的派生类可以选择返回DerivedReturnObject&。如果在派生类中没有声明,编译器不知道方法的返回类型是什么。它不能假设它与基础中的相同,因此编译器需要一个原型。

有关使用协变返回类型覆盖的规则,请参阅此问题

于 2013-03-15T00:29:28.713 回答
0

想象一下,您的“祖父”类中有 4 个方法,它们都是纯虚拟的。现在您打算在“父亲”类中实现两个,在“子”类中实现两个。顺便说一句,这个特定的术语并不理想,但我坚持使用它,因为你是从它开始的。

当您定义类时(在 .h 文件之间class whatever {,并};告诉编译器您打算覆盖基类(术语中的祖父类)中的哪些函数。您可以将实现放在标题中,或者您可以将它放在 .cpp 文件中。但是您必须指定要覆盖的 4 个函数(如果有)中的哪一个。

如果您没有在类定义中(如您所说的在头文件中)提到特定的继承函数,那么当编译实现(.cpp 文件)时,编译器会说“什么?你从来没有告诉我你打算覆盖这个功能!” 您的回答似乎是“伙计,它是纯虚拟的,如果我不覆盖它,我就无法实例化对象!” 但你知道吗?编译器对此毫不在意。它不知道您是否要创建可实例化的对象,是否打算进一步继承,甚至不知道该函数在基类中是纯虚拟的。它所知道的是,它正在编译一个实现文件,它遇到了一个你没有在类定义中告诉它的函数。

因此,当您编写class something {...};类定义时,请务必列出您打算为该类实现的所有功能,无论您是否继承了它们。不要列出您继承的未更改且不会实现的函数。如果您继承的是一个接口(所有函数都是纯虚拟的),那么是的,如果您要全部实现它们,则必须在类中列出它们。那是因为语言没有为这种情况留出特殊情况。这根本不等同于“我的头文件必须是祖父类头文件的精确副本”,我认为如果您在祖父类中有更多函数并且没有实现它们,您会更清楚地看到这一点都在孩子班。

于 2013-03-15T14:26:21.493 回答