gpt4 book ai didi

c++ - 在派生虚函数中强制执行正确的参数类型

转载 作者:可可西里 更新时间:2023-11-01 18:25:54 26 4
gpt4 key购买 nike

我发现这个问题很难简洁地描述,所以我附上了一个演示程序的代码。

一般的想法是我们想要一组派生类,这些派生类被迫从基类实现一些抽象的 Foo() 函数。每个派生的 Foo() 调用都必须接受不同的参数作为输入,但所有参数也应该从 BaseInput 类派生。

到目前为止,我们看到了两种可能的解决方案,但我们都不太满意:

  1. 从基类中删除 Foo() 函数,并在每个派生类中使用正确的输入类型重新实现它。然而,这消除了在每个派生类中以相同方式实现它的强制。

  2. 在接收函数中进行某种动态转换,以验证接收到的类型是否正确。但是,这并不能防止程序员出错并传递不正确的输入数据类型。我们希望传递给 Foo() 函数的类型在编译时是正确的。

是否有某种模式可以强制执行这种行为?这整个想法是否打破了 OOP 的某种基本思想?我们真的很想听听您对我们提出的解决方案之外的可能解决方案的意见。

非常感谢!

#include <iostream>

// these inputs will be sent to our Foo function below
class BaseInput {};
class Derived1Input : public BaseInput { public: int d1Custom; };
class Derived2Input : public BaseInput { public: float d2Custom; };

class Base
{
public:
virtual void Foo(BaseInput& i) = 0;
};

class Derived1 : public Base
{
public:
// we don't know what type the input is -- do we have to try to cast to what we want
// and see if it works?
virtual void Foo(BaseInput& i) { std::cout << "I don't want to cast this..." << std::endl; }

// prefer something like this, but then it's not overriding the Base implementation
//virtual void Foo(Derived1Input& i) { std::cout << "Derived1 did something with Derived1Input..." << std::endl; }
};

class Derived2 : public Base
{
public:
// we don't know what type the input is -- do we have to try to cast to what we want
// and see if it works?
virtual void Foo(BaseInput& i) { std::cout << "I don't want to cast this..." << std::endl; }

// prefer something like this, but then it's not overriding the Base implementation
//virtual void Foo(Derived2Input& i) { std::cout << "Derived2 did something with Derived2Input..." << std::endl; }
};

int main()
{
Derived1 d1; Derived1Input d1i;
Derived2 d2; Derived2Input d2i;

// set up some dummy data
d1i.d1Custom = 1;
d2i.d2Custom = 1.f;

d1.Foo(d2i); // this compiles, but is a mistake! how can we avoid this?
// Derived1::Foo() should only accept Derived1Input, but then
// we can't declare Foo() in the Base class.

return 0;
}

最佳答案

由于您的 Derived是一个 Base 类,它应该永远不要收紧基础契约(Contract)前提条件:如果它必须表现得像一个 Base,它应该接受 BaseInput 没问题。这就是著名的里氏替换原则。

虽然您可以对参数进行运行时检查,但您永远无法实现完全类型安全的方法:您的编译器可能会在看到 时匹配 DerivedInput Derived 对象(静态类型),但它不知道 Base 对象后面的子类型是什么...

要求

  1. DerivedX 应该采用 DerivedXInput
  2. DerivedX::Foo 应该是接口(interface)等于 DerivedY::Foo

矛盾:Foo 方法是根据 BaseInput 实现的,因此在所有派生类中具有相同的接口(interface),或者 DerivedXInput 类型不同,它们不能具有相同的接口(interface)。

在我看来,这就是问题所在。

在编写在类型未知框架中处理的紧密耦合的类时,我也遇到了这个问题:

class Fruit {};
class FruitTree {
virtual Fruit* pick() = 0;
};
class FruitEater {
virtual void eat( Fruit* ) = 0;
};

class Banana : public Fruit {};
class BananaTree {
virtual Banana* pick() { return new Banana; }
};
class BananaEater : public FruitEater {
void eat( Fruit* f ){
assert( dynamic_cast<Banana*>(f)!=0 );
delete f;
}
};

还有一个框架:

struct FruitPipeLine {
FruitTree* tree;
FruitEater* eater;
void cycle(){
eater->eat( tree->pick() );
}
};

现在这证明了一个太容易被破坏的设计:设计中没有任何部分将树木与食客对齐:

 FruitPipeLine pipe = { new BananaTree, new LemonEater }; // compiles fine
pipe.cycle(); // crash, probably.

您可以通过将其设为模板来提高设计的凝聚力,并消除对虚拟调度的需求:

template<class F> class Tree {
F* pick(); // no implementation
};
template<class F> class Eater {
void eat( F* f ){ delete f; } // default implementation is possible
};
template<class F> PipeLine {
Tree<F> tree;
Eater<F> eater;
void cycle(){ eater.eat( tree.pick() ); }
};

这些实现实际上是模板特化:

template<> class Tree<Banana> {
Banana* pick(){ return new Banana; }
};


...
PipeLine<Banana> pipe; // can't be wrong
pipe.cycle(); // no typechecking needed.

关于c++ - 在派生虚函数中强制执行正确的参数类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7181361/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com