0

假设我有以下 3 个类(因为这是一个示例,它不会编译!):

class Base
{
public:
   Base(){}
   virtual ~Base(){}
   virtual void DoSomething() = 0;
   virtual void DoSomethingElse() = 0;
};

class Derived1
{
public:
   Derived1(){}
   virtual ~Derived1(){}
   virtual void DoSomething(){ ... }
   virtual void DoSomethingElse(){ ... }
   virtual void SpecialD1DoSomething{ ... }
};

class Derived2
{
public:
   Derived2(){}
   virtual ~Derived2(){}
   virtual void DoSomething(){ ... }
   virtual void DoSomethingElse(){ ... }
   virtual void SpecialD2DoSomething{ ... }
};

我想根据某些在运行时才可用的设置创建 Derived1 或 Derived2 的实例。

由于直到运行时我才能确定派生类型,所以您认为以下做法是不好的做法吗?...

class X
{
public:
   ....

   void GetConfigurationValue()
   {
      ....
      // Get configuration setting, I need a "Derived1"
      b = new Derived1();

      // Now I want to call the special DoSomething for Derived1
      (dynamic_cast<Derived1*>(b))->SpecialD1DoSomething();      
   }
private:
   Base* b;
};

我通常读到 dynamic_cast 的用法很糟糕,但正如我所说,直到运行时我才知道要创建哪种类型。请帮忙!

4

7 回答 7

6

为什么不通过将派生的指针分配给指向基的指针来延迟“丢弃”一些类型信息的时刻:

void GetConfigurationValue()
{
  // ...
  // Get configuration setting, I need a "Derived1"
  Derived1* d1 = new Derived1();
  b = d1;

  // Now I want to call the special DoSomething for Derived1
  d1->SpecialD1DoSomething();
}
于 2010-06-17T20:08:56.740 回答
3

虚函数的意义在于,一旦你有了正确的对象,你就可以调用正确的函数,而无需知道这个对象是哪个派生类——你只需调用虚函数,它就会做正确的事。

dynamic_cast当您有一个派生类定义了基类中存在的不同的东西,并且您需要/想要考虑额外的东西时,您才需要。

例如:

struct Base { 
    virtual void do_something() {}
};

struct Derived : Base { 
    virtual void do_something() {} // override dosomething
    virtual void do_something_else() {} // add a new function
};

现在,如果你只想打电话do_something(), adynamic_cast是完全没有必要的。例如,您可以拥有一个 的集合Base *,然后只在每个集合上调用do_something(),而无需注意该对象是真的 aBase还是 a Derived

当/如果你有 a Base *,并且你想调用do_something_else()那么你可以使用 adynamic_cast来确定对象本身是否真的是 aDerived这样你就可以调用它。

于 2010-06-17T20:11:26.567 回答
2

使用 dynamic_cast 本身并不是坏习惯。不恰当地使用它是不好的做法,即在并不真正需要它的地方。

以这种方式使用它也是一种不好的做法:

(dynamic_cast<Derived1*>(b))->SpecialD1DoSomething();  

原因:dynamic_cast(b) 可能返回 NULL。

使用 dynamic_cast 时,您必须格外小心,因为不能保证 b 实际上是 Derived1 类型而不是 Derived2 类型:

void GenericFunction(Base* p)
{
    (dynamic_cast<Derived1*>(b))->SpecialD1DoSomething();
}

void InitiallyImplementedFunction()
{
   Derived1 d1;
   GenericFunction(&d1); // OK... But not for long. 
   // Especially, if implementation of GenericFunction is in another library
   // with not source code available to even see its implementation 
   // -- just headers
}    

void SomeOtherFunctionProbablyInAnotherUnitOfCompilation()
{
   Derived2 d2;
   GenericFunction(&d2); // oops!
}

您必须检查 dynamic_cast 是否真的成功。有两种方法:在演员表之前和之后检查。在转换之前,您可以检查您尝试转换的指针是否实际上是您通过 RTTI 期望的指针:

if (typeid(b) == typeid(Derived1*))
{
   // in this case it's safe to call the function right 
   // away without additional checks
   dynamic_cast<Derived1*>(b)->SpecialD1DoSomething();
}
else
{
  // do something else, like try to cast to Derived2 and then call
  // Derived2::SpecialD2DoSomething() in a similar fashion
}

事后检查实际上要简单一些:

Derived1* d1 = dynamic_cast<Derived1*>(b);
if (d1 != NULL)
{
   d1->SpecialD1DoSomething();
}

我还要说在 C++ 中编程时尝试保存输入是一种不好的做法。C++ 中有许多功能似乎完全可以缩短输入(即让您觉得'NULL 永远不会在这里发生'),但后来调试起来却很痛苦。;)

于 2010-06-17T20:47:05.330 回答
2

您可能需要考虑的其他一些事情以避免使用 dynamic_cast

来自 Effective C++ (第三版) - Item 35 Alternatives to virtual functions -

  1. 通过非虚拟接口 (NVI) 的“模板方法模式”。使用公共方法“包装器”使虚拟功能成为私有/保护 - 允许您在虚拟方法之前和之后强制执行一些其他工作流程。
  2. 通过函数指针的“策略模式”。将额外的方法作为函数指针传入。
  3. 通过 tr1::function 的“策略模式”。类似于 2. 但您可以为整个班级提供各种选项
  4. 《战略格局》经典。将策略与主类分开 - 将虚函数推入另一个层次结构。
于 2010-06-17T20:49:13.093 回答
1

出什么问题了:

Base * b;
if( some_condition ) {
   b = new Derived1;
}
else {
   b = new Derived2;
}

if ( Derived2 * d2 = dynamic_cast <Derived2 *>( b ) ) {
    d2->SpecialD2DoSomething();
}

还是我错过了什么?

OI 是否可以建议,在发布此类问题时,您(和其他人)将您的类 A、B、C 等命名,并将您的函数命名为 f1()、f2() 等。这让回答您问题的人的生活变得更加轻松.

于 2010-06-17T20:04:44.143 回答
1

有一种名为Factory Pattern的模式适合这种情况。这允许您根据某些输入参数返回正确类的实例。

享受!

于 2010-06-17T20:08:44.430 回答
1

避免的一种方法dynamic_cast是拥有一个虚拟蹦床函数“SpecialDoSomething”,其派生多态实现调用该特定派生类的“SpecialDxDoSomething()”,它可以是您想要的任何非基类名称。它甚至可以调用多个函数。

于 2010-06-17T20:10:49.027 回答