【问题标题】:Invoke a method of templated derived class method from non-template base class pointer从非模板基类指针调用模板派生类方法的方法
【发布时间】:2020-12-12 11:48:25
【问题描述】:

我正在尝试在我的项目中实现一个类似于 Qt 中的属性系统的属性系统。我们刚开始提出一些想法,正处于原型设计阶段。 基本上,我从 Qt 中了解到的是,客户端应该能够通过 .h 文件中的一些宏来传递 get 函数、set 函数和属性类型。所以我试着模仿。

以下是我的示例代码:

抽象的 getter 类。这种类型的getter类是Property Class的成员

class AbstractFunc
{
public:
  template < typename R > 
  R Invoke ()
  {
    return (this)->Invoke ();
  }
};

获取函数模板:返回值可以是 T , T&, const T& , T* 等。

template < typename R, class T > class GetterFunction : public AbstractFunc
{
  typedef R (T::*GetterFunc) ();

public:
  GetterFunction (T * obj, GetterFunc func):m_Obj (obj), m_Func (func)
  {
  }

  R Invoke ()
  {
    return m_Obj->*(m_Func) ();
  }

public:
  T * m_Obj;
  GetterFunc m_Func;
};

属性类:

class Property
{
public:
  Property (string name, AbstractFunc* getter):m_name (name), m_getter (getter)
  {

  }

  template < typename R > R GetValue ()
  {
    return m_getter->Invoke < R > ();
  }

private:
  string m_name;
  AbstractFunc* m_getter;
}; 

一些窗口类:

class Window
{
public:

};

示例窗口类

class CheckBox :public Window
{
public:
  int GetChecked ()
  {
    return m_checked;
  }
  void SetChecked (int nChecked)
  {
    m_checked = nChecked;
  }

  void AddProperty (string name)
  {
    m_prop = new Property (name, new GetterFunction< int, Checked >(this, &Checked::GetChecked));
  }

  int m_checked;
  Property *m_prop;
};

主要功能:

int main ()
{

  CheckBox cc;
  cc.AddProperty ("Hello");

  cout<<"value:"<< cc.m_prop->GetValue<int>();

  return 0;
}

问题: Getter 函数在属性类中被记住为 AbstractFunc。我想在 AbstractFunc* 实例上调用“Invoke”,它应该调用成员函数并返回正确的返回类型。上面的代码在 AbstractFunc::Invoke 处抛出错误。

see live

【问题讨论】:

  • return (this)-&gt;Invoke (); 这毫无意义。该函数只是调用自身。也许您想让这个函数成为纯虚拟函数。不用等你不能,它是一个模板。看起来你根本没有这样的功能。 “它应该调用成员函数并返回正确的返回类型”正确的类型是什么?
  • @n.'pronouns'm。所需的接口看起来很像std::anystd::any_cast,您可以在其中传递您期望的类型,然后从中获取值。如果您对类型有误,则无法获得值。当然可以实现。 OP 的做法完全是错误的。
  • @n.'pronouns'm。是的,我不能让它成为虚函数。return (this)->Invoke ();实际上,如果我转换为派生类,这可以调用派生类函数。好的,但这是错误的。关于“它应该调用成员函数并返回正确的返回类型”,Invoke 函数应该返回正确的类型,例如:对于函数 int GetChecked(),Invoke() 应该返回 int; int& GetChecked(), Invoke() 应该返回 int&。
  • @HTNW 是的,当然可以实现。您可以为每个变量指定std::any 的类型,并在每次使用前执行any_cast。但你为什么要它?
  • "实际上,如果我转换为派生类,这可以调用派生类函数" OK 继续转换它,有什么问题?你知道派生类是什么吗?

标签: c++ templates c++17 variadic-templates pointer-to-member-functions


【解决方案1】:

您的AbstractFunc 根本不是抽象的:它的Invoke 不是虚拟的。所以即使GetterFunction 也有一个名为Invoke 的方法,该方法实际上并没有覆盖AbstractFunc::Invoke;它只是隐藏它。当您尝试通过AbstractFunc* 调用Invoke 时,它会调用AbstractFunc::Invoke,它会进入无限递归并因此产生UB。

我会按照@n.m. 的建议创建一个像这样的类层次结构:

class AbstractFunc {
  // lock down construction
  AbstractFunc() = default;
public:
  template<typename R>
  R Invoke();
  template<typename R>
  bool HasType() const noexcept;
  virtual ~AbstractFunc() = default; // need to have SOME virtual method so that we have runtime type info; also a virtual destructor is required anyway

  template<typename R>
  friend class TypedFunc;
};
template<typename R>
struct TypedFunc : AbstractFunc { // the ONLY instances of AbstractFunc are also instances of specializations of TypedFunc
  virtual R InvokeTyped() = 0;
};
// one kind of TypedFunc applies a getter on an object
template<typename R, typename T>
struct GetterFunc : TypedFunc<R> {
  // you never see a GetterFunc in the interface anyway... don't see a need to hide these
  T *obj; // have you considered std::shared_ptr?
  R (T::*getter)();
  GetterFunc(T *obj, R (T::*getter)()) : obj(obj), getter(getter) { }
  R InvokeTyped() override { return (obj->*getter)(); }
};
template<typename R, typename T>
std::unique_ptr<GetterFunc<R, T>> MakeGetterFunc(T *obj, R (T::*getter)()) {
  return std::make_unique<GetterFunc<R, T>>(obj, getter);
}
// another kind applies a functor, etc.
template<typename R, typename F>
struct FunctorFunc : TypedFunc<R> {
  F func;
  template<typename... G>
  FunctorFunc(G&&... args) : func(std::forward<G>(args)...) { }
  R InvokeTyped() override { return func(); }
};

这已经可用:如果您有一个AbstractFunc*AbstractFunc&amp;,您可以将dynamic_cast 降为预期类型的​​TypedFunc(例如TypedFunc&lt;int&gt;)。如果成功(您得到一个非空指针或没有std::bad_cast 异常),那么您只需调用InvokeTyped,而无需知道您实际处理的是哪种GetterFunc/FunctorFunc/。在 AbstractFunc 中声明的函数 InvokeHasType 只是帮助完成此任务的糖。

template<typename R>
bool AbstractFunc::HasType() const noexcept {
  return dynamic_cast<TypedFunc<R> const*>(this);
}
template<typename R>
R AbstractFunc::Invoke() {
  return dynamic_cast<TypedFunc<R>&>(*this).InvokeTyped();
  // throws std::bad_cast if cast fails
}

完成。

class Property {
  std::string name;
  std::unique_ptr<AbstractFunc> getter;
public:
  Property(std::string name, std::unique_ptr<AbstractFunc> getter) : name(std::move(name)), getter(std::move(getter)) { }
  template<typename R>
  bool HasType() const noexcept { return getter->HasType<R>(); }
  template<typename R>
  R GetValue() const { return getter->Invoke<R>(); }
  std::string const &GetName() const noexcept { return name; }
};
struct Window {
  virtual ~Window() = default;
  // doesn't really make sense to add/remove these from outside...
  virtual std::vector<Property> GetProperties() = 0;
};
class CheckBox : public Window {
  int checked = 0;
public:
  int GetChecked() /*const*/ noexcept { return checked; }
  void SetChecked(int checked) noexcept { this->checked = checked; }
  std::vector<Property> GetProperties() override {
    std::vector<Property> ret;
    ret.emplace_back("Boxes Checked", MakeGetterFunc(this, &CheckBox::GetChecked));
    return ret;
  }
};

int main() {
  CheckBox cb;
  cb.SetChecked(5);
  for(auto const &prop : cb.GetProperties()) std::cout << prop.GetName() << ": " << prop.GetValue<int>() << "\n";
}

然后您可以添加例如virtual std::type_info const&amp; GetType() const 或类似于 AbstractFunc 如果您希望能够直接获取类型等。

【讨论】:

  • 感谢@HTNW 展示示例。这行得通。唯一的问题是dynamic_cast。但是,我会按照你的建议尝试一些方法来避免它。感谢 cmets 并为您指明了前进的方向。将尝试在此基础上进行构建。
猜你喜欢
  • 2016-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-09
  • 2014-02-15
  • 2014-03-22
  • 2014-12-10
  • 2014-03-13
相关资源
最近更新 更多