【问题标题】:How to get instance of class template out of the if statement? (C++)如何从 if 语句中获取类模板的实例? (C++)
【发布时间】:2013-02-05 11:10:22
【问题描述】:

假设我有一个类模板,它有一个成员 pData,这是一个 AxB 任意类型数组 T

template <class T> class X{ 
public:
    int A;
    int B;
    T** pData;
    X(int a,int b);
    ~X();        
    void print(); //function which prints pData to screen

};  
template<class T>X<T>::X(int a, int b){ //constructor
    A = a;
    B = b;
    pData = new T*[A];
    for(int i=0;i<A;i++)
        pData[i]= new T[B];
    //Fill pData with something of type T
}
int main(){
    //...
    std::cout<<"Give the primitive type of the array"<<std::endl;
    std::cin>>type;
    if(type=="int"){
        X<int> XArray(a,b);
    } else if(type=="char"){
        X<char> Xarray(a,b);
    } else {
        std::cout<<"Not a valid primitive type!";
    } // can be many more if statements.
    Xarray.print() //this doesn't work, as Xarray is out of scope.
}

由于实例 Xarray 是在 if 语句中构造的,因此我无法在其他任何地方使用它。我试图在 if 语句之前创建一个指针,但由于此时指针的类型未知,我没有成功。

处理此类问题的正确方法是什么?

【问题讨论】:

  • 对此没有“明显”的答案,因为 C++ 是静态类型的。您不能提示用户输入类型,然后创建该类型并在其他地方使用它——您必须在编译时知道该类型!通常解决这种情况的一种技术是“类型擦除”,但这需要指定所有类型必须具有的一些公共元素,并且只能通过该公共元素进行接口(例如“可打印”)。
  • 在这里,this 可能会对您有所帮助。

标签: c++ class templates pointers scope


【解决方案1】:

这里的问题是X&lt;int&gt;x&lt;char&gt; 是完全不相关的类型。

它们都是同一个模板类的结果这一事实在这里无济于事。

我可以看到几种解决方案,但这些取决于您的真正需要。

例如,您可以使X&lt;&gt; 实例派生自具有print() 方法的通用非模板基类(最终作为纯虚拟)。但在你这样做之前,请确保它在功能层面上有意义:应该使用继承,因为它有意义,而不仅仅是因为技术限制。如果你这样做,你可能也会想要一个虚拟析构函数。

您还可以将std::function&lt;void ()&gt; 绑定并存储到您要调用的方法,但确保对象仍然“活动”(它们不在您当前的代码中:X&lt;int&gt;X&lt;char&gt;当它们超出范围时被销毁,在您实际调用 print() 之前)。

最终的解决方案是制作一些与X&lt;int&gt;X&lt;char&gt; 兼容的变体类型(boost::variant<> 可以在这里提供帮助)。然后,您可以编写一个为每种类型实现 print() 功能的访问者。

选择最后一个解决方案,它会变成这样:

typedef boost::variant<X<int>, X<char>> genericX;

class print_visitor : public boost::static_visitor<void>
{
public:
    template <typename SomeType>
    void operator()(const SomeType& x) const
    {
        // Your print implementation
        // x is your underlying instance, either X<char> or X<int>.
        // You may also make several non-templated overloads of
        // this operator if you want to provide different implementations.
    }
};

int main()
{
  boost::optional<genericX> my_x;

  if (type=="int") {
    my_x = X<int>(a,b);
  } else if(type=="char") {
    my_x = X<char>(a,b);
  }

  // This calls the appropriate print.
  if (my_x) {
    boost::apply_visitor(print_visitor(), *my_x)
  }
}

我们实际上缺乏给出明确答案的知识:如果您的类是“实体”,那么您可能应该去继承。如果它们更像“值类”,那么变体方式可能更适合。

【讨论】:

    【解决方案2】:

    C++ 是一种静态类型语言,这意味着您必须在编译时知道对象的类型。在这种情况下,您将根据用户输入构建对象的类型,因此无法在运行时知道类型。

    解决此问题的最常见方法是使用dynamic polymorphism,其中函数通过使用late binding 的公共接口调用。我们使用 virtual functions 在 C++ 中完成此操作。例如:

    struct IPrintable {
       virtual void print() = 0;
    };
    
    template<class T>
    class X : public IPrintable {
      // Same code as you showed above.
    };
    
    int main() {
      std::cout<<"Give the primitive type of the array"<<std::endl;
      std::cin>>type;
    
      std::unique_ptr<IPrintable> XArray;
    
      if(type=="int"){
          XArray.reset(new X<int>(a,b));
      } else if(type=="char"){
          XArray.reset(new X<char>(a,b));
      } else {
          std::cout<<"Not a valid primitive type!";
      } // can be many more if statements.
    
      Xarray->print() // this works now!
    }
    

    这解决了超出范围的问题,并允许您使用 XArray 变量的动态类型进行打印。虚拟函数是使这成为可能的秘诀。

    【讨论】:

    • 继承不是唯一可能的解决方案。在其他语言中,您可能别无选择,但在 C++ 中,有很多方法可以实现此结果。仅仅因为两个对象共享一个共同的属性就创建一个基类并强制继承对我来说似乎有点极端。更不用说这会以某种方式迫使类型成为“实体类”并阻止它们成为“值类”(它们不能轻易复制,并且比较变得更加复杂)。
    • 我并不是要暗示这是唯一的选择。但当其他因素不相关时,这通常是最简单的选择。鉴于 OP 的确切问题,没有理由使用更复杂的东西,如回调或访问者。此外,如果基类都是空接口,则复制问题没有实际意义,因为您不必担心切片。
    • 请注意,我也没有说这是一个糟糕的解决方案。我们只是不知道(OP 可能没有告诉我们他必须面对的所有限制)。访问者并不复杂。这实际上是我见过的最愚蠢的事情之一。只是我认为不应该鼓励学习 C++ 的人认为具有继承的面向对象编程是唯一的解决方案:我每周都会看到数十个 Java 程序员来 C++ 这么想,这真的很无聊.
    • 谢谢,所有的答案都非常有帮助。我选择了这个作为接受,因为这正是我的想法,但不知道是否以及如何完成。
    • 您不能使用这种技术直接访问 pData。如果您需要对 pData 做一些事情,那么有两个主要选项。第一个是扩展 printable 和/或定义另一个接口并让 XArray 实现它以执行您需要对 pData 执行的任何操作。第二种是使用其他解决方案中建议的访问者模式。
    【解决方案3】:

    与其尝试将模板放入 main,我会采取与其他建议相反的方式...将 main 的代码 out 移入它自己的(可能模板化) 需要处理单一类型的函数:

    template <typename T>
    void generateAndPrint(int a, int b) {
       X<T> x(a,b);
       x.print();
    }
    int main() { ...
       if (type=="int") generateAndPrint<int>(a,b);
       else if (type=="char") generateAndPrint<char>(a,b);
       else ...
    }
    

    【讨论】:

      【解决方案4】:

      如果您想使用不同的数组,无论它们的类型如何,单独的模板都无法帮助您。目前X&lt;int&gt;X&lt;char&gt;之间完全没有关系。

      如果您想将它们视为公共类型的两个子类型,则必须使用继承(以及动态分配的变量)。例如,所有X&lt;T&gt; 都可以继承同一个基类,比如Printable,您可以将数据存储在unique_ptr&lt;Printable&gt; 中:

      unique_ptr<Printable> r;
      if(type=="int"){
          r.reset(new X<int>(a,b));
      } else if(type=="char"){        
          r.reset(new X<char>(a,b);
      }
      r->print();
      

      但这可能不是最好的设计。

      一个可能更好的解决方案是,而不是尝试在 if 之外工作,而是将所有工作移到 if 内部。在您的简单示例中,这可以通过复制调用 print 来完成,但这也不是很好。但是,按照这个想法,我们可以创建一个模板函数来完成这项工作:

      template<class T>
      void workWithType(int a, int b)
      {
         X<T> Xarray(a, b);
         Xarray.print();
      }
      
      //...
      
      if(type=="int"){
          workWithType<int>(a,b);
      } else if(type=="char"){
          workWithType<char>(a,b);
      } 
      

      【讨论】:

      • 继承并不一定意味着他必须使用动态分配。
      猜你喜欢
      • 2020-02-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多