【问题标题】:C++ passing unknown type to a virtual functionC ++将未知类型传递给虚函数
【发布时间】:2018-02-15 19:06:37
【问题描述】:

我正在用 C++ 编写,我想将未知类型(仅在运行时已知)传递给纯虚函数:

virtual void DoSomething(??? data);

其中DoSomething 是派生类中纯虚函数的实现。

我计划使用模板,但事实证明虚函数和模板不能一起工作:Can a C++ class member function template be virtual?

我想避免对传递给函数的所有类使用基类(类似于 C# 中的 object)。

提前致谢

【问题讨论】:

  • 你能说得更具体些吗?如何在函数内部使用数据?数据类型的要求是什么?例如。只有几个已知的类,或者您打算接受任何具有特定方法等的类?
  • 您的问题需要缩小一点。你知道类型的范围,你想让它们被自动推导出来吗?最简单的答案是“使用 void*”,更好的答案可能是 Story Teller。这完全取决于您的用例。
  • 我很好奇,看着答案,是否有可能获得相同的结果而无需在 doSomething 上进行任何转换。类似于以某种方式封装类型而不必将doSomething 设为模板,并使用decltype 检索该类型,即一种虚拟工厂方法...
  • 发送未知类型参数有什么用?参数是用于使用的,因为已证实这看起来是一种错误的方法。
  • 使用动态多态实现双重调度的常用方法是访问者模式en.wikipedia.org/wiki/Visitor_pattern

标签: c++ templates virtual-functions


【解决方案1】:

您需要type erasure。这方面的一个例子是通用的boost::any(和 C++17 中的std::any)。

virtual void DoSomething(boost::any const& data);

然后每个子类可以尝试安全 any_cast 以获得它期望的数据。

void DoSomething(boost::any const& data) {
  auto p = any_cast<std::string>(&data);

  if(p) {
    // do something with the string pointer we extracted
  }
}

如果您寻求的行为范围受到更多限制,您当然可以推出自己的类型擦除抽象。

【讨论】:

    【解决方案2】:

    如果您不想使用任何 boost/C++17,请考虑从基类派生“doSometing”函数的参数,并对正确的类对象进行动态转换。在这种情况下,您可以在运行时检查您有一个有效的指针。

    class param{
    public:
        virtual ~param(){};
    };
    
    template <typename T>
    struct specificParam:param{
        specificParam(T p):param(p){}
        T param;
    };
    
    
    class Foo
    {
    public:
        virtual void doSomething(param* data) = 0;
    };
    
    template <typename T>
    class Bar : public Foo
    {
    public:
        virtual void doSomething(param* data){
            specificParam<T> *p = dynamic_cast<specificParam<T> *>(data);
    
            if (p != nullptr){
                std::cout<<"Bar got:" << p->param << "\n";
            }
            else {
                std::cout<<"Bar: parameter type error.\n";
            }
        }
    };
    
    int main(){
      Bar<char>   obj1;
      Bar<int>    obj2;
      Bar<float>  obj3;
    
      specificParam<char>   t1('a');
      specificParam<int>    t2(1);
      specificParam<float>  t3(2.2);
    
      obj1.doSomething(&t1); //Bar got:a
      obj2.doSomething(&t2); //Bar got:1
      obj3.doSomething(&t3); //Bar got:2.2
    
      // trying to access int object with float parameter
      obj2.doSomething(&t3); //Bar: parameter type error.
    }
    

    最简单(但不安全!)的方法是使用 void* 指针 + 静态转换

    class Foo
    {
    public:
        virtual void doSomething(void* data) = 0;
    };
    
    template <typename T>
    class Bar:public Foo
    {
    public:
        virtual void doSomething(void* data){
            T* pData = static_cast<T*>(data);
            std::cout<<"Bar1 got:" << *pData << "\n";
        }
    };
    
    int main(){
    
      Bar<char>  obj1;
      Bar<int>   obj2;
      Bar<float> obj3;
    
      char  c = 'a';
      int   i = 1;
      float f = 2.2;
    
      obj1.doSomething(&c); // Bar1 got:a
      obj2.doSomething(&i); // Bar1 got:1
      obj3.doSomething(&f); // Bar1 got:2.2
    
      //obj2.doSomething(&c); // Very bad!!!     
    }
    

    【讨论】:

      【解决方案3】:

      类型擦除不是唯一的可能性。

      您可能有兴趣使用访问者模式:将 std::variant 作为参数,并使用包含您要实现的模板代码的 lambda 访问它:

      virtual void doSomething(std::variant<int,float/*,...*/> data)
         {
         visit([=](auto v){/*...*/;},data);
         }
      

      【讨论】:

        【解决方案4】:

        类似的东西?:

        class Foo
        {
            virtual ~Foo() = 0;
        };
        
        template <typename T>
        class Bar : public Foo
        {
            T object;
        }
        
        ...
        
        virtual void DoSomething(Foo* data)
        {
            Bar<int>* temp = dynamic_cast<Bar<int>*>(data);
            if (temp)
                 std::count<<temp->object;
        }
        

        【讨论】:

        • Foo 至少需要一个虚拟成员函数(可能是析构函数),否则dynamic_cast 将不起作用。
        • I want to avoid using a base class for all the classes I pass to the function (something like object in C#).
        • 这不需要为传递给函数的每个对象提供基类,仅用于包装器Bar。虽然这在技术上是“传入的对象”,但它不太可能是 OP 的意思。
        • @AzCopey 从实际的角度来看,我认为在这种情况下包装和派生是相同的机制,因为doSomething 必须向下转换接收到的对象;但我明白你的意思。
        猜你喜欢
        • 1970-01-01
        • 2019-11-01
        • 2019-08-31
        • 1970-01-01
        • 2014-04-03
        • 1970-01-01
        • 1970-01-01
        • 2018-04-10
        • 2022-01-13
        相关资源
        最近更新 更多