【问题标题】:How to get pointer to child from method returning pointer to parent如何从返回指向父级的指针的方法中获取指向子级的指针
【发布时间】:2018-05-20 07:14:06
【问题描述】:

上下文

上下文由三个类组成:

  • 抽象父级(例如Player
  • 孩子(例如TapePlayer
  • 持有人(例如MyMachine

持有者有一个成员变量,它是父类的shared_ptr<...>,它的设置器接受子类的shared_ptr<...>s。

我的 getter 看起来像这样

shared_ptr<Parent>  getChildPtr() {
    return parentPtr;
  };

但是它返回一个指向父对象的指针,并且不能访问子方法。
如果我想做如下的事情

holder.getChildPtr()->childMethod(); 
// ERROR! No member named 'childMethod' in 'Parent'

我应该如何实现 getter 来获取指向子类而不是父类的指针?

代码

整个代码如下所示:

class Player {
public:
  Player(){};
  virtual ~Player{};

  virtual void play() = 0;
}

class TapePlayer : public Player {
public:
  TapePlayer(){};
  virtual ~TapePlayer{};

  void play() { ... };
  void rewind() { ... };
}

class MyMachine {
public:
  MyMachine(); //
  ~MyMachine();

  void setPlayer(shared_ptr<Player> p) {
    playerPtr = p;
  }

  shared_ptr<Player>  getPlayer() {
    return playerPtr;
  };

private:
  shared_ptr<Player>  playerPtr;
}


MyMachine machine; // the holder

shared_ptr<TapePlayer> tapePtr(new TapePlayer()); // pointer to child

machine.setPlayer(tapePtr); // set holder with pointer to child

machine.getPlayer()->rewind(); // -- ERROR! No member named 'rewind' in 'Player'
// if I want to get the player of that machine to rewind I need
// to dynamic_cast<TapePlayer>() ...

我很确定有比强制转换为子类型更好的方法。有什么想法吗?

编辑

这是一个非常简单的例子。我真正想做的是:

我的持有者类名为 Clip。一个剪辑播放一些东西,无论是 图像,视频,图像序列,某种处理 OpenCv,一个矢量形状...任何可以显示的东西。

所有这些类型的东西都是玩家。
我不在乎剪辑包含什么类型的播放器。我只是想让它展示给我看。但是,有些播放器需要在运行时进行调整,例如 OpenCv,它需要调整参数以获得最佳处理。我无法在父类中实现所有子类的所有方法,这对我来说毫无意义。为什么视频需要有调整 OpenCv 参数的方法?
我所需要的只是两者都具有“可播放”方法,并且能够将它们存储在map&lt;string, PlayerPtr&gt; 中以随时访问它们或更改剪辑所持有的播放器。

【问题讨论】:

  • 你可以为你的父类添加一个虚拟倒带方法(只是声明)。

标签: c++ inheritance shared-ptr getter-setter getter


【解决方案1】:

关键是这种破坏了多态性-即使使用dynamic_cast,您仍然需要检查结果是否不为0(即检查实际类型)并且您可能已经知道,dynamic_cast 很有名因为速度非常慢(并且需要在可执行文件中构建 RTTI 信息)。

你有什么理由不能在你的Player 接口中添加一个纯虚拟的rewind() 方法吗?然后你只需调用它,继承的类可以在这种情况下做它决定的任何事情。其他子类可能将其实现为空(或者在 Player 本身默认情况下它甚至可以是空的,因此子类在不需要时不必实现它)。甚至可能还有一些更“通用”的虚函数,例如 reset()restart() 等,它们只会在底层调用 rewind() 来获取 TapePlayer

您当然可以更喜欢更复杂的解决方案,例如访问者/观察者(TapePlayerRewindObserver 并观察 rewind 事件)等。

编辑:

因此,为了解决编辑 cmets - 如果需要调整不同的类型,那么再次,您可以只使用一个虚拟方法 tweak()(纯或默认为空 impl)并进行任何需要的调整。否则,您最终会得到一长串 ifs 并根据实际类型调用调整方法。

如果调整需要一些特殊参数,那么情况可能会很困难......一种选择可能是有一个调整参数接口(并用它调用调整方法),但如果参数不能统一你无论如何都需要在调整方法中进行动态转换以转换为正确的参数类型(这基本上会导致双重调度,这在 C++ 中需要在某些时候进行转换)......但无论如何这仍然需要在调用网站不是很好。

这还取决于您何时真正需要设置调整参数 - 创建实例时是否足以设置所有内容(并且调整参数之后不会更改),或者是否需要稍后更改它们。如果只需要在启动时设置,那么您可以为不同的对象类型设置工厂类,并且工厂可以设置参数。

(从技术上讲,您甚至可以以类似的方式处理更改参数的必要性,通过为各种玩家类型保留设置对象类型,玩家也会保持对它们的引用,在创建对象时分配,一旦他们需要更改,您可以更改设置并调用tweak()update() 或类似的函数来通知对象某些设置已更改并需要重新应用)

【讨论】:

  • 你真的需要一个dynamic_cast吗?即使是 C 风格(或 C++ 风格)的静态转换也可以(我知道没有强制执行类型检查)。我同意这会在一定程度上破坏多态性。
  • 但是对于静态转换,您需要提前知道它是哪种类型。如果假设Mp3Player 不是从TapePlayer 继承的,那么将其静态转换为TapePlayer 将是未定义的行为(转换后的实例甚至可能没有rewind() 方法),并且程序可能会崩溃。这就是为什么需要 dynamic_cast 的原因,因为如果类型不能被转换,它会返回空指针以便检查。
  • 如果 Mp3Player 不是 TapePlayer 的子类,则 static_cast 不会编译(C++ 风格)。即使使用 dynamic_cast,您仍然需要指定类型(例如 dynamic_cast(basePtr)。我在这里遗漏了什么吗?
  • 我用这个作为一个非常简化的例子。从玩家继承的实际类彼此非常不同。我将进行编辑以澄清我正在尝试做的事情
  • @dsp_user 关键是static_cast&lt;TapePlayer *&gt;(playerPtr) 不会知道它是从Mp3Player 转换的,因为playerPtr 将被输入Player * (或shared_ptr 类似的情况)。然后static_cast 将编译,因为它被允许从Player * 转换为TapePlayer *,并且无法知道它实际上是具有相同基数但不相关的类型的实例。对于dynamic_cast,你仍然需要指定类型,但它会对实际类型进行运行时检查,如果类型是不同的继承类,它将返回0(或抛出)。
猜你喜欢
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多