【问题标题】:Is it possible to "copy" an object whose templated class derives from a non-templated base?是否可以“复制”其模板类派生自非模板基类的对象?
【发布时间】:2012-05-20 13:33:41
【问题描述】:

我用 C++ 构建了一个小型、范围有限的跨平台 UI 库。它使用以下类来处理 UI 回调:

// Base class for templated __Callback class to allow for passing __Callback
// objects as parameters, storing in containers, etc. (as Callback*)
class Callback
{
    public:
        virtual void execute() = 0;
        virtual ~Callback() { }

    protected:
        Callback() { }
};

正如评论所描述的,这是回调的基类 - 它允许将它们作为参数传递并将它们存储在 UI 小部件中,以便可以执行正确的回调(例如,当用户单击按钮时)。

// C++ has no callbacks (in the sense of an action [method] of a
// target [object]), so we had to roll our own. This class can be used
// directly, but the NEW_CALLBACK macro is easier.
template <class __TargetType>
class __Callback : public Callback
{
    public:
        typedef __TargetType TargetType;
        typedef void (TargetType::*ActionType)(void);

        virtual void execute()
        {
            (this->__target->*this->__action)();
        }

        __Callback(TargetType* target_, ActionType action_) :
        Callback(), __target(target_), __action(action_) { }

        virtual ~__Callback() { }

    private:
        // target object for the callback
        TargetType* __target;

        // action (method) of the target that will be called
        ActionType __action;
};

这个模板类是回调范式的核心。它存储了指向对象的指针和指向成员函数的指针,以便以后可以在目标对象上调用成员函数。

#define NEW_CALLBACK(class_, obj_, act_) \
    new __Callback<class_>(obj_, &class_::act_)

这个宏只是让创建模板化__Callback 对象变得更容易一些。

这已经很长时间了!带有回调的按钮可能被实例化为:

MyClass* obj = new MyClass();
Button* btn = new Button("Title", NEW_CALLBACK(MyClass, obj, btnClicked));

这将创建一个按钮,稍后将其放置在窗口或其他容器中,单击时将调用obj-&gt;btnClicked()

现在,我的问题(对于冗长的设置感到抱歉,我认为我不能再削减它了)。出现了我需要复制Callback* 对象的情况。当然,由于它只是一个指向基类的指针,我无法确定模板化派生类的类型。

如何复制任意的Callback 对象,副本指向与原始对象相同的目标和操作? 或者,是否有一种完全不同的方法来解决这个回调问题?我应该服用(尽管我不想改变太多)?

谢谢大家!

【问题讨论】:

  • 类型擦除对Callback有严格要求吗?不相关:标识符中的两个前导下划线保留用于实现。
  • 天哪,请绝对不要这样命名你的类型和变量!任何带有双下划线(__thiseven__this)或前导下划线 + 大写字母(_This)的标识符都保留用于实现,这有点像你在双重打破该规则。
  • 我没有意识到存在这种约定/限制,感谢您指出。我一直在为私有成员使用双下划线前缀,为受保护成员使用单下划线前缀,而为公共成员使用无前缀。同样,参数的单下划线后缀和引用返回的双下划线后缀。我的例子被简化了;这个项目的真正源代码在几个命名空间内,大多数标识符都以项目代码为前缀(例如“__abcMember”)。无论如何,您是否有一个很好的链接来描述此限制+任何范围考虑等?
  • 命名空间和作用域无关紧要,因为宏完全忽略了它们。如果实现定义的宏意外地与您的成员或其他任何名称具有相同的名称,那么您就有麻烦了。

标签: c++ class templates polymorphism


【解决方案1】:

我不知道您是否应该采用更好的方法,但这似乎是克隆方法的理想用途。

您只需在 __Callback 模板中定义一个复制构造函数,在您的基类中定义一个纯虚拟 Clone 方法,然后在您的模板中实现该虚拟方法(利用您创建的复制构造函数)

例如:

class Callback
{
    public:
    ...
    virtual Callback* Clone()=0;
};

template <class __TargetType>
class __Callback : public Callback
{
public:
    ...
    __Callback(const __Callback& other) :
        __target(other.target_), __action(other.action_) { }

    virtual Callback* Clone()
    {
        return new __Callback(this);
    }
}

【讨论】:

  • 谢谢! clone() 方法很聪明;希望我能想到:)
  • @inspector-g - 它被称为“原型”。拿起 GoF 书。
【解决方案2】:

使用clone 作为虚拟构造函数的替代品。注意 使这真正起作用的协变返回类型。例如

struct Callback {
  virtual Callback* clone() const;
};

template<...>
struct Callback_impl {
  virtual Callback_impl* clone() const;
};

您还应该考虑使用shared_ptr 进行生命周期管理。全部 这似乎有点脆弱。

对我来说,您似乎想要 std::function 。它是多态的、类型安全的,并且通过std::mem_fn 与指向成员函数的指针一起工作。

【讨论】:

  • 感谢您的链接和代码!这两个答案都有帮助,但正如他们所说,只能有一个。关于shared_ptr,我完全同意。我最近一直在阅读它们和类似的范例,但没有使用它们。由于这个项目到目前为止,我最近决定在它之外玩弄它们以熟悉自己,然后在后续版本中使用它们。
  • 我添加了今天早上想到的一些东西。
猜你喜欢
  • 2019-11-09
  • 1970-01-01
  • 1970-01-01
  • 2016-08-17
  • 1970-01-01
  • 2014-03-13
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多