How to get instance of class template out of the i

2019-02-23 00:02发布

问题:

Suppose I have a class template which have a member pData, which is an AxB array of arbitary type 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.
}

As the instance Xarray is constructed inside of an if statement, I cannot use it anywhere else. I tried to make a pointer before the if statements but as the type of the pointer is unknown at that point, I did not succeed.

What would be a proper way of dealing with this kind of a problem?

回答1:

C++ is a statically typed language, meaning that you must know the type of objects at compile time. In this case you are basing the type of the object constructed on user input, so it's not possible to know the type at runtime.

The most common way to address this issue is to use dynamic polymorphism in which functions are invoked via a common interface using late binding. We accomplish this in C++ using virtual functions. For example:

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!
}

This solves the out of scope issue and allows you to print using the dynamic type of the XArray variable. Virtual functions are the secret sauce that make this possible.



回答2:

The problem here is that X<int> and x<char> are completely unrelated types.

The fact that they are both a result of the same templated class won't help here.

I can see several solutions, but those depends on what you really need.

You could, for instance make the X<> instances derive from a common non-templated base class that has the print() method (eventually as a pure virtual). But before you do that, be sure that it makes sense on a functional level: one should use inheritance because it makes sense, not solely because of technical constraints. And if you do that, you probably will want to have a virtual destructor as well.

You could also bind and store a std::function<void ()> to the method you want to call, but ensure that the objects are still "alive" (they aren't in your current code: both the X<int> and X<char> are destroyed when they go out of scope, way before you actually call print()).

A final solution would be to make some variant type that is compatible with both X<int> and X<char> (boost::variant<> can help here). You could then write a visitor that implements the print() functionality for each type.

Picking the last solution, it would become something like:

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)
  }
}

We actually lack the knowledge to give a definitive answer: if your classes are "entities", then you probably should go for inheritance. If they are more like "value classes", then the variant way might be more suited.



回答3:

Rather than trying to fit the templates into main I would go the opposite way than the rest of the suggestions... move the code out of main and into it's own (possibly templated) function that needs to deal with a single type:

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:

If you want to work with different arrays, whatever their type, templates alone cannot help you. Currently, there is exactly no relationship between X<int> and X<char>.

If you want to treat them as two subtypes of a common type, you will have to use inheritance (and dynamically allocated variables). For instance, all X<T> may inherit the same base class, say Printable, and you can store the data in a unique_ptr<Printable>:

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();

But this is probably not the best design.

A probably better solution would be, instead of trying to work outside of the if, to move all the work inside of the if. In your simple example, this could be done by duplicating the call to print, but this is not pretty nice either. But, going toward this idea, we can create a template function that does the job:

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);
}