在方法

时间:2015-11-26 20:01:32

标签: c++ inheritance interface abstract-class

修改

主要思想是创建一个允许添加新类型元素的界面。例如,添加字符串,使用“X”算法计算其距离。这就是我认为模板不能成为正确答案的原因。

我在Element类中更改了函数的距离定义,现在它已经实现了。

class Element : public Object
{
...
    virtual float distance(Element *other){return INFINITE;};
...
}

对于Vector类,它的变化相同:

class Vector : public Element
{
...
    virtual float distance(Element *other);
...
}

其实施是:

float Vector::distance(Element *other)
{   
    if(other->getClass() != this->getClass()) return INFINITE;//this came from Object class
    Vector *n_other = dynamic_cast<Vector*>(other);
    float result = this->L2D(*this,*n_other); 
    return result;
}

OLD

我正在尝试在C ++中使用接口,以便子类可以编写这些方法。

在这种情况下,距离表示一个值,表示两个元素的接近程度(相似)。

例如我现在正在尝试使用向量,但将来我会使用字符串或其他东西,如文档,面等等......它可以像元素一样使用。

//Element.h
class Element
{
    virtual float distance(Element const&, Element const&)=0;
};`

然后我有了类Vector

//Vector.h
#include "Element.h"
class Vector: public Element
{
    float distance(Vector const&, Vector const&);//(?)
};

实施

#include "Vector.h"
float Vector::distance(Element const &a, Element const &b)
{   
    return Vector::L2D(a,b);//Euclidean distance
}

我的问题是我怎么能这样做,因为我找不到这个问题的例子。 我不确定你是否能理解我想要做的事情......我希望如此。 谢谢大家。

3 个答案:

答案 0 :(得分:2)

您要求的内容称为argument covariance。也就是说,重写方法的参数随着继承的改变而改变。这在直觉上是有道理的,但可以是案例类型的安全问题。让我们假设它是可能的,并编译你的代码。那会发生什么呢?

Element* e = new Vector();
e->distance(Element(), Element()); // but Vector::distance expects an Element!

这种覆盖形式在我所知道的所有传统语言中都是非法的。为了使覆盖有效,您无法更改参数&#39;类型(有时您可能会更改返回类型)。

因此,您的选择是:

  1. 坚持使用基本签名,并使用dynamic_cast(尝试)向下覆盖覆盖方法的参数
  2. 避免使用纯虚函数,只需让每个类使用匹配的参数类型定义自己的方法。
  3. 在这种情况下,double-dispatch等模式有时会派上用场。
  4. 请注意,使用模板无法帮助您 - 虚拟功能不能成为模板。

答案 1 :(得分:1)

我从基类Element中看到的是,这是一个抽象类型,因为它的至少一个方法被声明为purely virtual。这意味着两件事:首先,您不能创建类型为Element的对象,其次,所有继承的类必须实现任何声明为pure virtual的函数。

在您的基类中,您将其作为声明:

virtual float distance(Element const&, Element const&)=0;

并且在派生类中,您将此作为声明:

float distance(Vector const&, Vector const&);//(?)

在派生类的实现中你有这个:

float Vector::distance(Element const &a, Element const &b) { 
    return Vector::L2D(a,b);//Euclidean distance
}

基类声明所有派生类必须实现的纯虚函数必须返回一个float,并且必须接受对Element类型的两个const引用。但是在你继承的类中,它的声明是另外说明的:它声明这个函数确实返回一个不是问题的浮点数,但是它的参数类型是因为它被声明为对Vector类型进行两次const引用。如果您使用的是MS Visual Studio 2012或更高版本,您可以尝试这样做:在继承的类中的函数声明之后添加关键字override,以便您继承的声明如下所示:

float distance(Vector const&, Vector const&) override;

然后尝试编译并查看是否有任何编译器,构建或链接错误。

下一步是更改派生类的声明和定义,以匹配基类的纯虚拟签名声明。执行此操作后,派生函数声明将如下所示:

float distance(Element const&, Element const&) override;

要了解这里发生了什么;这是我对原始代码所做的一个示例,因此您可以看到正在使用抽象类型和继承的内容。

<强> element.h展开

#ifndef ELEMENT_H
#define ELEMENT_H

class Element {
public: 
    virtual float distance( Element const&, Element const&  ) = 0;    
}; // Element

#endif // ELEMENT_H

<强> Element.cpp

#include "stdafx.h"
#include "Element.h"

// ----------------------------------------------------------------------------
// distance()
float Element::distance( Element const&, Element const& ) { 
    return 0;
} // distance

<强> Vector.h

#ifndef VECTOR_H
#define VECTOR_H

#include "Element.h"

class Vector : public Element {
public:
    float distance( Element const&, Element const& ) override;    
}; // Vector

#endif // VECTOR_H

<强> Vector.cpp

#include "stdafx.h"
#include "Vector.h"

// ----------------------------------------------------------------------------
// distance()
float Vector::distance( Element const&, Element const& ) {
    return 1;
} // distance

<强>的main.cpp

#include "stdafx.h"
#include "Vector.h"

int main() {
    float val = 0;

    Vector v1;
    Vector v2;
    Vector v3;
    val = v3.distance( v1, v2 );

    std::cout << val << std::endl;

    std::cout << "Press any key to quit" << std::endl;
    _getch();

    return 0;
} // main

很难确切地说出你在问什么以及你想要实现什么,但是从单独查看你的代码来看,你似乎正在使用继承的抽象类型,但你继承的成员函数签名是错误的,因为它与基数的纯虚方法签名不匹配。

如果仔细观察我提交给你的小程序;这个编译,构建和执行,我得到了适当的结果。

如果您注意到这里,声明期望两个const&到一个Element对象的类型,但是当我调用该方法时,我传入两个声明的变量类型或{{{的实例1}}对象。这是有效的,因为Vector对象是从Vector类型继承的。另外,如果查看输出值,则打印的值为1而不是0.当您尝试Element基类的函数声明时,这就是您想要的。

override返回0 - 无法调用它,因为它是纯虚方法,导致基类为抽象

Element::distance()返回1 - 这将在矢量对象上调用。

也许这会帮助你实现目标。

答案 2 :(得分:0)

当子类方法应该具有不同的签名时,我不确定接口的用途是什么。可能的是:

#include <iostream>

template <typename T>
struct DistInterf {
    virtual double distance(T other)=0;
};

struct A : DistInterf<A> {
    double distance(A other){
        return 2;
    }
};

int main() {
    A t,t2;
    std::cout << t.distance(t2) << std::endl;
    return 0;
}

但是,我没有看到在这里使用界面的任何好处,因为每个子类将实现不同的接口(因为我理解这是你要求的问题)。

我在评论中已经提到过:距离函数应该是静态的,或者只需要一个参数,否则它就没什么意义了。

PS:我能想到一个用例:

template<typename T> 
void foo(T t1,T t2){
    std::cout << t1.distance(t2) << std::endl;
}

您可以将实现上述interace的任何类型作为模板参数传递。