在接受指向抽象基类的指针的函数中使用派生类操作



假设我有一个抽象基类,它有两个派生类。每个派生类都有一些抽象基类中没有的新功能,但两个派生类都具有相同的功能。例如:

class MyBase:
public:
/* ... */
virtual void DoSomething() = 0;
/* ... */

class MyAlpha : public MyBase
public:
/* ... */
void DoSomething() { /* does sometihng */ }
/* Function not present in abstract base class */
void DoSomethingNew() { /* does something new */ }
/* ... */
class MyBeta : public MyBase
public:
/* ... */
void DoSomething() { /* does sometihng */ }
/* Function not present in abstract base class */
void DoSomethingNew() { /* does something new */ }
/* ... */

现在,我在某个地方有一个模板化函数,它接受指向基类的指针(在我的例子中是std::unique_ptr),我希望能够调用DoSomethingNew()函数(该函数存在于两个派生类中,但不存在于基类中。例如:

template <typename Base_t> void MyOperation(std::unique_ptr<Base_t> &base_object) { 
/* some ops */
base_object->DoSomethingNew();
}

我该怎么做?我觉得模板专业化可能是这里的发展方向,但我不太确定。我正在用一个新功能扩展一个开源库,所以我对可以/应该修改哪些现有代码以使我的功能发挥作用有限制。在我的实际用例中,基类是我希望避免修改的代码,但为了在这个库中通用,我的函数签名需要接受指向基类的指针。

由于基类是虚拟的,实际使用情况类似于:

std::unique_ptr<MyBase> object = std::unique_ptr<MyAlpha>(new MyAlpha);
MyOperation(object);

如何使用MyOperation()函数中的派生类功能来实现这一点?如果有什么不同的话,我必须保持C++11的兼容性。

这些派生类中的每一个都有一些抽象基类中没有的新功能,但两个派生类都有相同的功能。

然后在一个可能抽象的中间类中捕获它:

class MyMiddle : public MyBase {
public:
virtual void DoSomethingNew() = 0;
};
class MyAlpha : public MyMiddle {
public:
void DoSomething() override;
void DoSomethingNew() override;
};
class MyBeta : public MyMiddle {
public:
void DoSomething() override;
void DoSomethingNew() override;
};

通过这种方式,您可以通过引用MyMiddle来实现DoSomethingNew的通用功能,从而避免了可能出现的大量代码重复。

现在我有一个模板化函数,它接受指向基类的指针(在我的例子中是std::unique_ptr),我希望能够调用DoSomethingNew()函数。

由于您只有一个指向基类的指针,编译器不会开箱即用地允许您在基类上调用派生类的方法。但是,如果您希望实现实际上是派生类的实例,则可以强制转换为派生类。

使用dynamic_cast检查派生类是否为预期类型,如果是,则将其作为该类型使用。如果您100%完全确定参数将始终为派生类,则使用static_cast,无论现在还是将来。换句话说,不要。转到dynamic_cast

注意,dynamic_cast可用于原始指针,但不可用于unique_ptr。因此,您有两种选择:要么将唯一指针保留为基,要么使用原始指针派生以进行访问。或者在一个复杂的多步骤过程中投射指针。只有当您希望在需要派生类型的上下文中更长时间地保持指针时,后者才有意义。简单的情况如下:

void SomethingSimple(std::unique_ptr<MyBase> base) {
MyMiddle* derived = dynamic_cast<MyMiddle>(base.get());
if (derived == nullptr) {
// derived wasn't of the correct type, recover in a reasonable way.
return;
}
derived->DoSomethingNew();
}

更复杂的指针投射是这样的:

void SomethingComplicated(std::unique_ptr<MyBase> base) {
MyMiddle* derived = dynamic_cast<MyMiddle>(base.get());
if (derived == nullptr) {
// derived wasn't of the correct type, recover in a reasonable way.
return;
}
std::unique_ptr<MyMiddle> middle(derived);
// Here two unique_ptr own the same object, make sure not to throw exceptions!
base.release();  // Complete transfer of ownership.
SomethingThatNeedsTheNewFunction(middle);  // Pass ownership of middle type.
}

当然,std::unique_ptr确实允许自定义删除程序,这使得整个设置方式更加有趣。我建议您阅读以下代码的答案,这些代码正在传播deleter,同时构造指向派生类的唯一指针。只有当函数签名允许在其指针参数中使用非标准deleter时,这才是必要的。

您可以在没有MyMiddle类的情况下执行上述操作,使用对dynamic_cast的两个单独调用来尝试依次转换到每个派生类。但只要中产阶级和共享功能在概念上有意义,我就会支持它。若您执行了两个单独的强制转换,那个么您可以为这两种情况调用一个模板函数,并且该模板函数可以假设该函数存在,即使它将在不同的参数类型上操作。不过,对我来说,这不是一个很好的解决方案。

我觉得模板专业化可能是一种方法,但我不太确定。

如果调用方将实际派生类型作为参数的静态类型来调用函数,那么这将起作用。所以你可以做

template <typename Base_t> void MyOperation(std::unique_ptr<Base_t> &base_object) {
// Handle the case where DoSomethingNew is not an option.
}
template <> void MyOperation(std::unique_ptr<MyAlpha> &alpha_object) {
alpha_object->DoSomethingNew();
}
template <> void MyOperation(std::unique_ptr<MyBeta> &beta_object) {
beta_object->DoSomethingNew();
}

但是以下仍然不能调用专门的函数:

std::unique_ptr<MyBase> object(new MyAlpha());
MyOperation(object);

即使object动态地包含MyAlpha,它的静态类型也是指向MyBase的唯一指针,这就是驱动模板参数的原因。所以我看不出这样的专业化会对你有用。

dynamic_cast<>的存在是为了在需要从指向基的指针向下转换或交叉转换到派生类时使用。在你的例子中,它看起来像这样:

std::unique_ptr<MyBase> object = std::unique_ptr<MyAlpha>(new MyAlpha);
// ...
dynamic_cast<MyAlpha*>(object.get())->DoSomethingNew();

你可以在这里阅读更多关于它的内容,但正如我在评论中提到的,这些内容太多表明你有设计问题。特别是在这里,当您在两个派生类中都具有该功能时,它可以很容易地移动到基类中。

作为dynamic_cast<>的替代方案,由于您无法修改基类,您可以创建自己的基类,从不可修改的基类中继承,并将接口自定义为实际使用的接口。

class NewBase : public MyBase
{
public:
void DoSomething() = 0;
void DoSomethingNew() = 0;
};
std::unique_ptr<NewBase> object  = std::unique_ptr<MyAlpha>(new MyAlpha);
// ...
object->DoSomethingNew();

相关内容

最新更新