【问题标题】:Interfaces which use itself in methods在方法中使用自身的接口
【发布时间】:2016-02-29 23:54:03
【问题描述】:

编辑

主要想法是制作一个允许添加新类型元素的界面。例如添加字符串,使用“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;
}

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

在这种情况下,距离表示一个值,该值表示两个元素有多近(相似)。

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

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

然后我有类向量

//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
}

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

【问题讨论】:

  • 子类不编写方法,他们实现它们。为什么距离方法需要两个对象?我希望它要么是一个要么是静态的。
  • 顺便说一句,这个问题根本不清楚(至少对我来说)。你怎么能做什么?你找不到什么问题的例子?我没有发现任何问题。
  • 我想我现在慢慢开始明白关于...的问题是什么
  • 我不确定我是否理解。如果派生类函数没有不同的代码,则最好不要覆盖基类函数。如果确实如此,那么您就不想这样称呼基础。
  • Vector 没有实现虚方法距离。他们需要有相同的签名

标签: c++ inheritance interface abstract-class


【解决方案1】:

您要求的内容称为argument covariance。即,让覆盖方法的参数随着继承的变化而变化。这在直觉上是有道理的,但可以解决类型安全问题。让我们假设这是可能的,并且您的代码已编译。那么这里会发生什么?

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

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

因此,您的选择是:

  1. 坚持基本签名,并使用 dynamic_cast 来(尝试)向下转换覆盖方法上的参数
  2. 避免使用纯虚函数,只需让每个类定义自己的方法,并使用匹配的参数类型。
  3. double-dispatch 等模式有时可能会在这种情况下派上用场。

请注意,在这里使用模板对您没有帮助 - 虚函数不能是模板。

【讨论】:

  • 虚拟模板成员函数是不可能的,但模板类可以有虚拟函数(见here
  • @tobi303 肯定可以,但模板不能用作界面。
  • 好吧,一方面我完全同意你的看法。另一方面,我发现接口可以用作模板参数,就像我的答案中的示例一样,这很有趣
  • @tobi303,您的建议称为 CRTP 或静态多态性。非常有用的模式,但不能总是替代传统的继承。
  • 啊,太好了,谢谢命名它。我曾多次阅读 CRTP,但直到我重新发现它,我才明白这一点;)
【解决方案2】:

我可以从您的基类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
}

基类声明这个所有派生类都必须实现的纯虚函数必须返回一个浮点数,并且必须接受两个对 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

ma​​in.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&amp;Element 对象的类型,但是当我调用该方法时,我传递了两个声明的变量类型或 Vector 对象的实例。这是因为 Vector 对象继承自 Element 类型。此外,如果您查看输出值,打印的值是 1 而不是 0。当您尝试 override 基类的函数声明时,这就是您想要的。

Element::distance() 返回 0 - 这不能被调用,因为它是一个纯虚方法导致基类是抽象的

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

也许这会帮助你实现你所追求的目标。

【讨论】:

    【解决方案3】:

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

    #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;
    }
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-12-15
      • 1970-01-01
      • 2011-05-13
      • 1970-01-01
      • 2020-05-02
      • 1970-01-01
      • 2021-04-17
      相关资源
      最近更新 更多