【问题标题】:Is there any way to avoid declaring virtual methods when storing (children) pointers?有什么方法可以避免在存储(子)指针时声明虚拟方法?
【发布时间】:2013-02-25 13:03:27
【问题描述】:

我最近遇到了一个烦人的问题,我对自己的解决方法不满意:我有一个程序维护一个指向基类的指针向量,并且我在那里存储了所有类型的子对象指针。现在,每个子类都有自己的方法,主程序可能会或不会调用这些方法,具体取决于对象的类型(请注意,尽管它们都大量使用基类的通用方法,因此这证明了继承的合理性)。

我发现有一个“对象标识符”来检查类类型(然后调用或不调用方法)很有用,这已经不是很漂亮,但这不是主要的不便之处。主要的不便之处在于,如果我想真正能够使用基类指针调用派生类方法(或者甚至只是将指针存储在指针数组中),那么需要在基类中将派生方法声明为虚拟类。

从 C++ 编码的角度来看是有意义的.. 但这对我来说是不切实际的(从 开发 的角度来看),因为我计划在不同的环境中创建许多不同的子类文件,可能由不同的人制作,我不想每次都调整/维护基类,以添加虚拟方法!

如何做到这一点?本质上,我要问(我猜)是如何实现类似 Objective-C NSArrays 的东西——如果你向一个没有实现该方法的对象发送消息,那么什么都不会发生。

问候

【问题讨论】:

  • 您是否注意到编程网站上的代码比对所述代码的松散描述更好?
  • 使用段落!在一个段落中阅读这么多内容令人恐惧。
  • “如何实现Objective-C NSArrays之类的东西——如果你向一个没有实现该方法的对象发送消息,那么什么都不会发生”。我真的希望这是不可能的。我真的这样做了。
  • 这只是说明 NSArrays 必须有多棒。还有,为什么声明方法virtual不方便
  • 如果您对子类型有这样的贬低差异,那么它们可能一开始就不属于同一个层次结构。如果他们这样做了,那么请考虑扩展您的基本合同并提供模板实现,以在 detectable 缺乏支持的情况下怠慢非必需的实现(即 E_NOTIMPL 条件)。但是,我仍然认为,您应该认真考虑 (a) 发布代码,以及 (b) 重新考虑您的设计以更好地实现基层。

标签: c++ pointers virtual


【解决方案1】:

而不是这个:

// variant A: declare everything in the base class
void DoStuff_A(Base* b) {
  if (b->TypeId() == DERIVED_1) 
      b->DoDerived1Stuff();
  else if if (b->TypeId() == DERIVED_2) 
      b->DoDerived12Stuff();
}

或者这个:

// variant B: declare nothing in the base class
void DoStuff_B(Base* b) {
  if (b->TypeId() == DERIVED_1) 
      (dynamic_cast<Derived1*>(b))->DoDerived1Stuff();
  else if if (b->TypeId() == DERIVED_2) 
      (dynamic_cast<Derived2*>(b))->DoDerived12Stuff();
}

这样做:

// variant C: declare the right thing in the base class
b->DoStuff();

请注意,每个必须完成的事情在基础中都有一个虚拟函数。

如果您发现自己更喜欢变体 A 或 B 而不是变体 C,请停下来重新考虑您的设计。您将组件耦合得太紧,最终会适得其反。

我打算在不同的地方创建许多不同的儿童班 文件,可能由不同的人制作,我不想 每次调整/维护基类,添加虚拟方法!

您可以在每次添加派生类时调整 DoStuff,但调整 Base 是不行的。请问为什么?

如果您的设计不适合 A、B 或 C 模式,请展示您拥有的东西,因为如今千里眼是罕见的壮举。

【讨论】:

  • 我现在正在做一些与 A 模式非常相似的事情,我试图让 B 模式工作。谢谢!现在,您的观点非常好: >您可以在每次添加派生类时调整 DoStuff,但调整 Base 是不行的。请问为什么?
  • 原因是程序调用孩子的“不常见”方法的唯一地方是在一个独特的函数中,比如你的“DoStuff”,它可以被其他编码器看到,我更喜欢控制基类。对不起,我很抱歉 - 我稍后会尝试举一个例子来说明我在做什么,但基本上这些对象是具有常用“绘图”方法的小部件(实际上是一个嵌入式电子项目,绘图是用激光完成的);每个“对象”都有特定的方法来获取和查询感知数据,并且它们具有不同的行为(运动等)。
  • “UI 按钮”之类的(这是我想到 iOS 中的模型视图控制器和 UIButton 类中的设计方法的唯一原因......)。但是,是的,多亏了你的解释,我看得更清楚了:必须付出一些。需要维护基类或 DoStuff 类。
  • 我认为我说得不够清楚。不要变体 A(同时维护基类和 DoStuff),也不要变体 B(仅维护 DoStuff)。做变体 C。如果您认为不可能,请展示更多您的设计。
【解决方案2】:

您可以执行您在 C++ 中描述的操作,但不能使用函数。顺便说一句,这有点可怕,但我想可能在某些情况下这是一种合法的方法。

第一种方法:

定义一个带有类似boost::variant parseMessage(std::string, std::vector&lt;boost::variant&gt;); 的签名的函数,可能还有一系列在基类上具有通用签名的便利函数,并在基类上包含一个接受函子的消息查找表。在每个类的构造函数中,将其消息添加到消息表中,然后 parseMessage 函数将每条消息打包到该类的正确函数中。

它又丑又慢,但它应该可以工作。

第二种方法:

在层次结构中进一步定义虚函数,因此如果要添加int foo(bar*);,首先添加一个将其定义为虚拟的类,然后确保每个想要定义int foo(bar*); 的类都继承自它。然后,您可以在尝试调用int foo(bar*); 之前使用dynamic_cast 确保您正在查看的指针继承自此类。这些接口添加类可能是纯虚拟的,因此可以使用多重继承将它们混合到各个点,但这可能有其自身的问题。

这种方式不如第一种方式灵活,并且需要实现功能的类相互链接。哦,它仍然很丑。

但大多数情况下,我建议你尝试编写 C++ 代码,比如 C++ 代码而不是 Objective-C 代码。

【讨论】:

  • 谢谢!我已经尝试过 dynamic_cast 策略——但我仍然需要添加(纯虚拟方法)并跟踪其他潜在贡献者添加的这些虚拟方法。您的第一个解决方案看起来很有趣!我会考虑的。顺便说一句,我不是软件开发人员 - 我现在正在 ARM Cortex 微控制器上开发应用程序。不,它不是 iphone(mbed)。而且,我绝对不是用 Objective-C 风格编码的!这只是一个问题。看起来人们对编程范式的破坏反应非常强烈。有趣
【解决方案3】:

这可以通过添加某种内省功能和元对象系统来解决。这个演讲Metadata and reflection in C++ — Jeff Tucker 演示了如何使用 c++ 的模板元编程来做到这一点。

如果您不想自己实现一个,那么使用现有的会更容易,例如Qtmeta object system。请注意,由于元对象编译器的限制,此解决方案不适用于多重继承:QObject Multiple Inheritance

安装后,您可以查询方法的存在并调用它们。手动执行此操作相当繁琐,因此调用此类方法的最简单方法是使用signal and slot 机制。

还有GObject,很相似,还有其他。

【讨论】:

  • 这看起来棒极了!谢谢你的链接。在计算机上开发时我会考虑这一点——我现在正在开发一个 mbed 微控制器(cortex m3),它可能没有足够的内存来使用这些库。 (我希望我能投票给你,但我是堆栈溢出的新手,系统还拒绝我的投票)
【解决方案4】:

如果您是planning to create many different children classes in different files, perhaps made by different people,我猜您也不想更改每个子类的主代码。然后我认为你需要在你的基类中做的是定义几个(不是很多)虚拟函数(具有空实现)但是这些函数应该用于在逻辑中标记它们被称为“AfterInsart”的时间或“分拣前”等
通常,您希望派生类在逻辑中执行自己的逻辑的地方并不多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-12
    • 2011-07-18
    • 1970-01-01
    • 2010-09-18
    • 1970-01-01
    • 2020-09-07
    相关资源
    最近更新 更多