【问题标题】:C++ templates with derived classes带有派生类的 C++ 模板
【发布时间】:2013-11-18 09:32:45
【问题描述】:

我正在编写一个库,其中有一个 BaseClass 类。任何使用该库的人都将创建自己的继承自 BaseClass 的类。我还有一个类,我们称之为 Manager,它包含一个 BaseClass 指针向量,它可以包含从 BaseClass 派生的任何对象。

Manager 类必须处理添加到其 BaseClass 向量中的任何对象的创建和销毁。这是因为vector中的任何对象都可以随时删除,Manager本身也可以删除。正因为如此,库的用户不能通过将一个指向从 BaseClass 派生的现有对象的指针传递给 Manager 的 baseClass 向量来添加对象。实际上,我可以允许用户这样做。但这将涉及复制一个虚拟对象,我不想这样做。

为了解决这个问题,我正在尝试使用模板函数。当试图将它添加到管理器的向量时,用户应该传递从 BaseClass 派生的对象的类型。这是我目前拥有的。

//Manager.h
#include <vector>
#include "BaseClass.h"
#include <typeinfo>

class Manager {
    //Vector holding pointers to any objects inherited from BaseClass
    vector<BaseClass*> baseClasses;

    //Template function that needs to add NEW object to baseClass vector
    //This I'm having problems with
    template<class T>
    BaseClass* Add() {
        BaseClass* baseClass = new T();
        baseClasses.push_back(baseClass);
        return baseClass;
    }

    //Template function that gets object from baseClass vector
    //This works fine
    template<class T>
    BaseClass* Get() {
        for (int i = 0; i < baseClasses.size(); i++) {
            if (typeid(*baseClasses[i]) == typeid(T)) {
                return baseClasses[i];
            }
        }
        return NULL;
    }
};

例如,用户在向 Manager 的 baseClass 向量添加或获取对象时应该这样做。 DerivedClass 派生自 BaseClass

Manager manager;
//Add a new DerivedClass object to Manager's vector
manager.Add<DerivedClass>();
//Get a pointer to the DerivedClass object that was just added
DerivedClass* derivedClass = (DerivedClass*)manager.Get<DerivedClass>();

我的 Get() 函数工作正常。我需要知道的是,我怎样才能让我的 Add() 函数工作?非常感谢您的帮助。

【问题讨论】:

  • 修复代码以反映什么是类型的成员,什么不是。您还应该尝试进一步解释为什么您认为管理器需要创建对象,以及向量中的任何对象都可以随时删除的确切含义,谁将做删除? -- 你的Add 函数有什么问题?
  • 您的Add 函数有哪些问题?根据您的问题,我知道它不起作用,但我想了解更多详细信息,例如它是否无法编译/链接?它会导致分段错误吗?它是否返回错误的值?
  • 如果你有一个指向基类的指针并且基类有一个虚析构函数,那么通过指针删除对象是安全的。
  • @DaleWilson:这取决于很多事情......例如,如果用户获取指针并将其删除,那么下一个 Get 请求可能会崩溃,所以这还不够 安全。但是这个问题没有很好的定义,所以很难提供一个好的答案。
  • 我编辑了它。那是你想要的吗?我的库是一个基于组件的游戏引擎。 Manger 类实际上是一个 GameObject,而 BaseClass 实际上是一个 Component。库的用户可以创建新组件以添加到游戏对象。并回答谁将进行删除......任何事情都可以。任何东西都可以删除对象,任何东西都可以从游戏对象中删除组件

标签: c++ templates inheritance derived-class polymorphism


【解决方案1】:

您的设计有很多不清楚的地方,但如果问题是您是否可以强制成员函数 templa Add 中的类型 T 可以强制从 BaseClass 派生,则选项是简单:

  • 什么都不做,编译器会很乐意在BaseClass* baseClass = new T(); 线上抱怨
  • 添加一个静态断言以使这一点更明显
  • 使用花哨的 SFINAE 技巧从重载集中删除函数

我会选择前两个中的一个。静态断言可以拼写为:

static_assert(std::is_base_of<BaseClass,T>::value);

SFINAE 技巧,我真的会避免它,因为它会使代码更加混乱,但可以实现为:

template <class T>
typename std::enable_if<std::is_base_of<BaseClass,T>::value,T*>::type
Add() {
   baseClasses.push_back(new T());
   return baseClasses.back();
}

(注意我把返回类型改成了T*,对象是T,为什么要返回BaseClass*?同样适用于Get函数返回@987654331没有意义@,当你知道对象真的是T)

现在实际问题要复杂得多,因为您的设计正在积极避免考虑所有权,这是您不应该的。考虑对象的所有权并确保有明确的所有者(或资源是共享的)。一旦您知道谁拥有这些对象,就为其余代码创建一个协议,以便在需要删除对象时通知所有者。如果你允许任何代码删除你持有的指针,你很快就会遇到未定义的行为。

其他较小的问题可能包括您正在强制所有组件都具有默认构造函数,这可能合适也可能不合适。您可以通过使用带有指针的非模板化 Add 并让调用者以他们喜欢的方式创建对象来简化此限制。

typeid 的使用通常是一种代码气味,我认为这不是例外,也许你最好设计一个类型层次结构,你可以询问对象是什么,而不是运行 @ 987654335@。如果你真的下定决心要做基于类型的接口,那就考虑dynamic_cast会不会更好。它会更加低效,但如果你有多个继承级别,它会让你返回最派生的对象作为中间对象

【讨论】:

  • 我正在使用 static_assert 方法,而 new T() 正在创建 BaseClass 类型的对象,而不是派生类。你知道这是为什么吗?我知道我的设计选择会受到质疑。但经理(或实际上是游戏对象)拥有这些对象。但是任何对象都可以告诉经理删除它们
  • @JanzDott:说任何人都可以删除对象和说任何人都可以要求经理删除对象之间有很大的区别
  • 是的,我知道。对不起,我说错了
猜你喜欢
  • 1970-01-01
  • 2017-06-24
  • 1970-01-01
  • 1970-01-01
  • 2016-02-17
  • 2015-04-10
  • 1970-01-01
  • 2011-02-19
  • 1970-01-01
相关资源
最近更新 更多