从子类到超类再到子类的转换?

发布于 2024-12-13 03:33:22 字数 1099 浏览 1 评论 0原文

我的程序需要处理不同类型的“注释”:NoteShortNoteLong...不同类型的注释应该以不同的方式在 GUI 中显示。我定义了这些笔记的基类,称为 NoteBase

我将这些注释存储在 XML 中;我有一个类,它从 XML 文件中读取并将笔记数据存储在 vector中。列表。然后我发现我无法获取它们自己的类型,因为它们已经转换为 NoteBase *

虽然 if(dynamic_cast(ptr) != NULL) {...} 可能有效,但它真的太丑了。实现以 NoteShort *NoteLong * 作为参数的函数不起作用。那么,有什么好的办法来解决这个问题呢?

更新:谢谢你们的回复。我认为这也不应该发生——但它确实发生了。我用另一种方式实现了它,现在它可以工作了。然而,据我所知,我确实在 NoteBase 中声明了(纯)虚函数,但忘记在派生类的标头中再次声明它。我想这就是导致问题的原因。

更新2(重要): 我从 C++ Primer 中找到了这段引用,可能对其他人有帮助:

有时更令人惊讶的是,对 即使基指针或 引用实际上绑定到派生对象:

 Bulk_item 批量;
 Item_base *itemP = &bulk; // ok: 动态类型是 Bulk_item
 Bulk_item *bulkP = itemP; // 错误:无法将基数转换为派生数

编译器无法在编译时知道特定的 转换实际上在运行时是安全的。编译器只看 在指针或引用的静态类型上确定是否 转换是合法的。在这些情况下,当我们知道转换 从基类到派生类是安全的,我们可以使用 static_cast (第 5.12.4,p。 183) 覆盖编译器。或者,我们可以请求一个在运行时检查的转换,方法是使用 dynamic_cast,第 18.2.1 节(第 773 页)对此进行了介绍。

My program needs to handle different kinds of "notes": NoteShort, NoteLong... Different kinds of notes should be displayed in the GUI in different ways. I defined a base class of these notes, called NoteBase.

I store these notes in XML; and I have a class which reads from the XML file and store notes' data in vector<NoteBase *> list. Then I found I cannot get their own types, because they are already converted to NoteBase *!

Though if(dynamic_cast<NoteLong *>(ptr) != NULL) {...} may works, it's really too ugly. Implementing functions take NoteShort * or NoteLong * as parameter don't work. So, any good way to deal with this problem?

UPDATE: Thank you guys for replying. I don't think it should happen neither -- but it did happened. I implemented it in another way, and it's now working. However, as far as I remember, I indeed declared the (pure) virtual function in NoteBase, but forgot to declare it again in headers of the deriving classes. I guess that's what caused the issue.

UPDATE 2 (IMPORTANT):
I found this quotation from C++ Primer, which may be helpful to others:

What is sometimes a bit more surprising is that the restriction on
converting from base to derived exists even when a base pointer or
reference is actually bound to a derived object:

 Bulk_item bulk;
 Item_base *itemP = &bulk;  // ok: dynamic type is Bulk_item
 Bulk_item *bulkP = itemP;  // error: can't convert base to derived

The compiler has no way to know at compile time that a specific
conversion will actually be safe at run time. The compiler looks only
at the static types of the pointer or reference to determine whether a
conversion is legal. In those cases when we know that the conversion
from base to derived is safe, we can use a static_cast (Section
5.12.4, p. 183) to override the compiler. Alternatively, we could request a conversion that is checked at run time by using a
dynamic_cast, which is covered in Section 18.2.1 (p. 773).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

冷血 2024-12-20 03:33:22

这里有两个重要的思路和代码,所以首先是最短的:


您可能不需要进行备份。如果所有 Note 都提供统一的操作(例如 Chime),那么您只需:

class INote
{
    virtual void Chime() = 0;
};

...
for_each(INote * note in m_Notes)
{
    note->Chime();
}

并且每个 Note 都会 Chime 应该如此,使用内部信息(例如持续时间和音高)。

这是干净、简单的,并且需要最少的代码。然而,这确实意味着所有类型都必须提供并继承自特定的已知接口/类。


现在,当您确实需要知道类型并转换回它时,就会出现更长且更复杂的方法。有两种主要方法,以及一个可以与 #3 一起使用或组合的变体 (#2):

  1. 这可以在编译器中使用 RTTI(运行时类型信息)来完成,从而允许它安全地 dynamic_cast< /code> 充分了解允许的内容。然而,这只适用于单个编译器或者单个模块(DLL/SO/等)。如果您的编译器支持它并且 RTTI 没有明显的缺点,那么它是迄今为止最简单的,并且需要您做的工作最少。但是,它不允许类型识别自身(尽管 typeof 函数可能可用)。

    这是按照您的方式完成的:

    NewType * obj =dynamic_cast(obj_oldType);
    

  2. 为了使其完全独立,向基类/接口添加虚拟方法(例如,Uuid GetType() const;)允许对象识别自身随时。与第三种(真正的 COM)方法相比,这有一个好处,但也有一个缺点:它允许对象的用户对要做什么做出明智的、也许更快的决定,但需要 a) 他们进行强制转换(这可能需要和不安全的 reinterpret_cast 或 C 风格转换)和 b) 该类型无法进行任何内部转换或检查。

    ClassID id = obj->GetType();
    if (id == ID_Note_Long)
        NoteLong * note = (NoteLong*)obj;
        ...
    
  3. COM 使用的选项是提供 RESULT /* success */ CastTo(const Uuid & type, void ** ppDestination); 形式的方法。这允许类型 a) 在内部检查强制转换的安全性,b) 根据自己的判断在内部执行强制转换(有关于可以做什么的规则),以及 c) 如果强制转换不可能或失败,则提供错误。然而,它 a) 阻碍了用户表单的良好优化,b) 可能需要多次调用才能找到成功的类型。

    NoteLong * note = nullptr;
    if (obj->GetAs(ID_Note_Long, ¬e))
        ...
    

以某种方式组合后两种方法(例如,如果传递 00-00-00-0000 Uuid 和 nullptr 目标,则用类型自己的 Uuid 填充 Uuid)可能是最佳方法识别和安全转换类型。后两种方法以及它们的组合都是独立于编译器和 API 的,甚至可以小心地实现语言独立性(就像 COM 所做的那样,以限定的方式)。

ClassID id = ClassID::Null;
obj->GetAs(id, nullptr);
if (id == ID_Note_Long)
    NoteLong * note;
    obj->GetAs(ID_Note_Long, ¬e);
    ...

当类型几乎完全未知时,后两者特别有用:提前不知道源库、编译器甚至语言,唯一可用的信息是提供给定的接口。使用如此少的数据并且无法使用 RTTI 等高度特定于编译器的功能,因此需要对象提供有关其自身的基本信息。然后,用户可以要求对象根据需要进行自我转换,并且对象可以完全自行决定如何处理。这通常与大量虚拟类甚至接口(纯虚拟)一起使用,因为这可能是用户代码可能拥有的所有知识。

在您的范围内,此方法可能对您没有用处,但可能会引起您的兴趣,并且对于类型如何识别自身并从基类或接口“向上”强制转换来说当然很重要。

There are two significant trains of thought and code here, so shortest first:


You may not need to cast back up. If all Notes provide a uniform action (say Chime), then you can simply have:

class INote
{
    virtual void Chime() = 0;
};

...
for_each(INote * note in m_Notes)
{
    note->Chime();
}

and each Note will Chime as it should, using internal information (duration and pitch, for example).

This is clean, simple, and requires minimal code. It does mean the types all have to provide and inherit from a particular known interface/class, however.


Now the longer and far more involved methods occur when you do need to know the type and cast back up to it. There are two major methods, and a variant (#2) which may be used or combined with #3:

  1. This can be done in the compiler with RTTI (runtime type information), allowing it to safely dynamic_cast with good knowledge of what is allowed. This only works within a single compiler and perhaps single module (DLL/SO/etc), however. If your compiler supports it and there are no significant downsides of RTTI, it is by far the easiest and takes the least work on your end. It does not, however, allow the type to identify itself (although a typeof function may be available).

    This is done as you have:

    NewType * obj = dynamic_cast<NewType*>(obj_oldType);
    
  2. To make it entirely independent, adding a virtual method to the base class/interface (for example, Uuid GetType() const;) allows the object to identify itself at any time. This has a benefit over the third (true-to-COM) method, and a disadvantage: it allows the user of the object to make intelligent and perhaps faster decisions on what to do, but requires a) they cast (which may necessitate and unsafe reinterpret_cast or C-style cast) and b) the type cannot do any internal conversion or checking.

    ClassID id = obj->GetType();
    if (id == ID_Note_Long)
        NoteLong * note = (NoteLong*)obj;
        ...
    
  3. The option which COM uses is to provide a method of the form RESULT /* success */ CastTo(const Uuid & type, void ** ppDestination);. This allows the type to a) check the safety of the cast internally, b) perform the cast internally at its own discretion (there are rules on what can be done) and c) provide an error if the cast is impossible or fails. However, it a) prevents the user form optimizing well and b) may require multiple calls to find a succesful type.

    NoteLong * note = nullptr;
    if (obj->GetAs(ID_Note_Long, ¬e))
        ...
    

Combining the latter two methods in some fashion (if a 00-00-00-0000 Uuid and nullptr destination are passed, fill the Uuid with the type's own Uuid, for example) may be the most optimal method of both identifying and safely converting types. Both the latter methods, and them combined, are compiler and API independent, and may even achieve language-independence with care (as COM does, in qualified manner).

ClassID id = ClassID::Null;
obj->GetAs(id, nullptr);
if (id == ID_Note_Long)
    NoteLong * note;
    obj->GetAs(ID_Note_Long, ¬e);
    ...

The latter two are particularly useful when the type is almost entirely unknown: the source library, compiler, and even language are not known ahead of time, the only available information is that a given interface is provided. Working with such little data and unable to use highly compiler-specific features such as RTTI, requiring the object to provide basic information about itself is necessary. The user can then ask the object to cast itself as needed, and the object is has full discretion as to how that's handled. This is typically used with heavily virtual classes or even interfaces (pure virtual), as that may be all the knowledge the user code may have.

This method is probably not useful for you, in your scope, but may be of interest and is certainly important as to how types can identify themselves and be cast back "up" from a base class or interface.

鲜肉鲜肉永远不皱 2024-12-20 03:33:22

使用多态性来访问每个派生类的不同实现,如下例所示。

class NoteBase
{
  public:
    virtual std::string read() = 0;
};

class NoteLong : public NoteBase
{
  public:
    std::string read() override { return "note long"; }
};

class NoteShort : public NoteBase
{
  public:
    std::string read() override { return "note short"; }
};

int main()
{
  std::vector< NoteBase* > notes;
  for( int i=0; i<10; ++i )
  {
    if( i%2 )
      notes.push_back(new NoteLong() );
    else
      notes.push_back( new NoteShort() );
  }

  std::vector< NoteBase* >::iterator it;
  std::vector< NoteBase* >::iterator end = notes.end();
  for( it=notes.begin(); it != end; ++it )
    std::cout << (*it)->read() << std::endl;

  return 0;
}

Use polymorphism to access different implementations for the each of the derived classes like in the followin example.

class NoteBase
{
  public:
    virtual std::string read() = 0;
};

class NoteLong : public NoteBase
{
  public:
    std::string read() override { return "note long"; }
};

class NoteShort : public NoteBase
{
  public:
    std::string read() override { return "note short"; }
};

int main()
{
  std::vector< NoteBase* > notes;
  for( int i=0; i<10; ++i )
  {
    if( i%2 )
      notes.push_back(new NoteLong() );
    else
      notes.push_back( new NoteShort() );
  }

  std::vector< NoteBase* >::iterator it;
  std::vector< NoteBase* >::iterator end = notes.end();
  for( it=notes.begin(); it != end; ++it )
    std::cout << (*it)->read() << std::endl;

  return 0;
}
梦毁影碎の 2024-12-20 03:33:22

正如其他人指出的那样,您应该尝试以一种无需强制转换即可完成所需所有操作的方式来设计基类。如果这是不可能的(也就是说,如果您需要特定于子类的信息),您可以像您所做的那样使用强制转换,也可以使用双重分派。

As others have pointed out, you should try to design the base-class in a way that lets you do all the stuff you require without casting. If that is not possible (that is, if you need information specific to the subclasses), you can either use casting like you have done, or you can use double-dispatch.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文