【问题标题】:Clone abstract base class (without meddling with derived)克隆抽象基类(不干预派生)
【发布时间】:2010-08-17 19:09:47
【问题描述】:

我遇到了一个具有挑战性的问题,该问题一直无法解决 - 希望到现在为止。我正在开发自己的框架,因此试图为用户提供灵活性以及所有代码复杂性。

首先我有一个用户可以实现的抽象基类,显然是简化的:

class IStateTransit
{
public:
    bool ConnectionPossible(void) = 0;
}

// A user defines their own class like so
class MyStateTransit : public IStateTransit
{
public:
    bool ConnectionPossible(void){ return true; }
}

接下来,我定义了一个工厂类。用户可以注册他们自己的自定义状态传输对象,稍后只需使用他们选择的字符串标识符来引用它们:

class TransitFactory : public Singleton<TransitFactory>
{
public:
    template<typename T> void RegisterStateTransit(const string& name)
    {
        // If the transit type is not already registered, add it.
        if(transits.find(name) == transits.end())
        {
            transits.insert(pair<string, IStateTransit*>(name, new T()));
        };
    }

    IStateTransit* TransitFactory::GetStateTransit(const string& type) const
    {
        return transits.find(type)->second;
    };

private:
    map<string, IStateTransit*> transits;
}

现在的问题是(可能很明显)当用户通过调用GetStateTransit 请求中转时,系统当前会不断返回相同的对象——指向相同对象的指针。我想改变它。

问题:如何在用户不必定义自己的复制构造函数或virtual constructor 的情况下返回原始IStateTransit 对象的新(克隆)。理想情况下,我希望GetStateTransit 方法能够将 IStateTransit 对象转换为它在运行时的派生类型并返回该实例的克隆。最大的障碍是我不希望用户必须实现任何额外(并且可能很复杂)的方法。

4 小时的谷歌搜索和尝试让我无处可去。有答案的就是英雄!

【问题讨论】:

  • 顺便说一句:IStateTransit::ConnectionPossible 等基类成员需要是虚拟的。此外,您应该提供一个虚拟析构函数(它什么都不做),以便 delete 通过基指针对派生实例进行良好定义。
  • @Evan:你是对的,我也是。我没有复制就输入了代码并忘记了......

标签: c++ copy abstract-class clone


【解决方案1】:

问题在于您没有执行克隆的类型信息,因为您只有一个指向基类类型的指针,并且不知道哪些派生类型已实现且可用。

我认为 4 小时的谷歌搜索没有出现任何结果是有原因的。如果您希望IStateTransit 可克隆,则必须有一个接口,其中派生类实现者提供某种克隆方法实现。

如果这不是您想听到的,我很抱歉。

但是,实现克隆方法不应该是一个很大的负担。只有类实现者知道如何复制一个类,给定一个正确的复制构造函数,可以像这样为叶节点类实现克隆:

Base* clone() const
{
    return new MyType(*this);
}

您甚至可以对其进行宏化;虽然我不会。

【讨论】:

  • 我猜你是对的。我想我会在我的基类中添加一个额外的纯虚拟 clone() 方法。 Arrrgh,我只是希望有更清洁的方式来安排事情,仍然。或者一些(可能是肮脏的)解决方法,例如存储在 IStateTransit 注册期间使用的类型,以某种方式将其转换为字符串并稍后使用该字符串进行类型转换......不过感谢您的努力。
  • @Waldo Spek:不过,从根本上说,您甚至不知道可能存在哪些派生类,因此在设计/实现派生类之前,您不可能生成正确的类复制代码.您没有必须使克隆函数成为虚拟方法,您可以将执行克隆的用户提供的函数指针与指向类的指针一起存储,但我认为它不会无论如何都要整洁。
  • @Waldo:您似乎不了解“静态类型”。类型是在编译时确定的,转换的目的地也是如此。字符串永远不能用作类型。在某个时候,您必须委托给一个知道静态类型以及如何复制它的函数。 clone 方法可以做到这一点。
  • 我省略的是我的 IStateTransit 类包含一些属性。假设它包含一个布尔值。我只希望我的新克隆/副本包含该布尔值。现在,我想说,因为无论如何我正在创建用户定义派生类的新实例,所以我至少有一些类型信息。由于我只关心将抽象基类中的信息复制到克隆中,因此我认为可能有一种方法。我很好地意识到我正在推动我的运气。 (我实际上只是想要复制构建我的基类)
  • @GMan:是的,我理解这个概念。但我的想法有点拒绝相信我坚持这种解释,因为我的派生类的实例是在运行时通过使用模板函数创建的。我只是希望我能捕获用户告诉程序的信息,即: factory->RegisterStateTransit("linear"); ...捕获“LinearStateTransit”。但又一次:我意识到我在推我的运气。
【解决方案2】:

如果我正确理解了这个问题,您不应该将new T -s 插入到地图中,而应该插入创建新 T-s 的对象。

struct ICreateTransit
{
    virtual ~ICreateTransit() {}
    virtual IStateTransite* create() const = 0;

};

template <class T>
struct CreateTransit: public ICreateTransit
{
    virtual IStateTransit* create() const { return new T(); }
};

现在插入:

transits.insert(pair<string, ICreateTransit*>(name, new CreateTransit<T>()));

并通过以下方式检索“副本”:

return transits.find(type)->second->create(); //hopefully with error handling

修改StateTransit&lt;T&gt; 应该不是不可能的,所以它拥有一个可以复制的T,如果默认的不这样做的话。

我认为此类技术的通用名称称为“类型擦除”(派生类型“记住”特定类型,尽管基类不知道这些)。

【讨论】:

  • 嘿哇,从这里判断,这实际上可能是解决方案!我的手指很痒,但我会在明天尝试执行此操作并回复您。现在需要好好睡一觉了:)
【解决方案3】:

对我来说,这个问题听起来abstract factory 模式可能会有所帮助。使用此模式,库客户端可以定义您的框架如何构建其类型。客户可以将自己的工厂子类注入到框架中,并在那里定义应该构建的类型。

您需要的是(另外) 工厂的基类 作为客户:派生一个混凝土工厂 一种将工厂的子类型(作为客户端)注入框架的方法 调用工厂方法来创建新类型。

这对你有帮助吗?

【讨论】:

  • 嗯,这个——或类似的——是我已经研究过的。问题是(就像查尔斯贝利发布的克隆情况一样)用户仍然必须实现他们自己的方法版本。似乎不可避免。
猜你喜欢
  • 2012-12-15
  • 2016-07-11
  • 2017-06-21
  • 1970-01-01
  • 1970-01-01
  • 2012-05-21
  • 1970-01-01
  • 2017-07-25
  • 1970-01-01
相关资源
最近更新 更多