Downcasting from base pointer to templated derived

2019-03-09 14:35发布

问题:

I have the following hierarchy:

class base
{
public:
   virtual ~base(){}
   virtual void foo() {}
};

template <typename T>
class derived1 : public base
{
   virtual void foo() {};
};

template <typename T>
class derived2 : public base
{
   virtual void foo() {};
};

Now given a pointer to base, I'd like to find out if the underlying is either derived1 or derived2. The problem is that both derived1 and derived2 can be specialised on many different types, using dynamic_cast to test for a down cast requires the template type to be know. I've ended up with messy, unmaintable and incomplete bit of code:

base* b = new derived1<int>();

if (dynamic_cast<derived1<int>*> ||
    dynamic_cast<derived1<unsigned int>*> ||
    dynamic_cast<derived1<double>*>)
  std::cout << "is derived1";
else if (dynamic_cast<derived2<int>*> ||
    dynamic_cast<derived2<unsigned int>*> ||
    dynamic_cast<derived2<double>*>)
  std::cout << "is derived2";

Is there a better way, that can handle any type specialization?

回答1:

Move the logic which depends on the type into the type.

Instead of:

if (dynamic_cast<derived1<int>*>(b) ||
    dynamic_cast<derived1<unsigned int>*>(b) ||
    dynamic_cast<derived1<double>*>(b))
  std::cout << "is derived1";
else if (dynamic_cast<derived2<int>*>(b) ||
    dynamic_cast<derived2<unsigned int>*>(b) ||
    dynamic_cast<derived2<double>*>(b))
  std::cout << "is derived2";

add a virtual print_name() const function to base, and then do:

void example() {
    std::unique_ptr<base> b(new derived1<int>());
    b->print_name();
}
class base
{
public:
   ~base(){}
   virtual void foo() {}
   virtual void print_name() const = 0;
};

template <typename T>
class derived1 : public base
{
   virtual void foo() {}
   virtual void print_name() const {
       std::cout << "is derived1";
   }
};

template <typename T>
class derived2 : public base
{
   virtual void foo() {}
   virtual void print_name() const {
       std::cout << "is derived2";
   }
};


回答2:

Insert a non-templated class inbetween base and derived1 or derived2:

class base
{
public:
   virtual ~base() {}  // **NOTE** Should be virtual
   virtual void foo() {}
};

class derived1_base : public base
{
};

template <typename T>
class derived1 : public derived1_base
{
public:
   virtual void foo() {}
};

class derived2_base : public base
{
};

template <typename T>
class derived2 : public derived2_base
{
public:
   virtual void foo() {}
};

In a comment, you mentioned:

[I want to] call a particular function for each one - btw there's more than derived1 and derived2

Add that (virtual) function to derived1_base, and you don't even need to know T anymore.

if (dynamic_cast<derived1_base*>(foo))
{
  std::cout << "is derived1";
  dynamic_cast<derived1_base*>(foo)->specific_derived1_function();
}
else if (dynamic_cast<derived2_base*>(foo))
{
  std::cout << "is derived2";
  dynamic_cast<derived2_base*>(foo)->specific_derived2_function();
}

NOTE: I consider a list of dynamic_cast<> a code smell, and I urge you to rethink your approach.



回答3:

You could add a virtual method to do a meta-type check of some kind:

class base
{
public:
    ~base(){}
    virtual void foo() {}
    virtual bool isa(const char* type_to_test){
          return strcmp(type_to_test,"base")==0;}
};

template <typename T>
class derived1 : public base
{
   virtual void foo() {};
   virtual bool isa(const char* type_to_test){
   return strcmp(type_to_test,"derived1")==0;}
};


回答4:

Solution 1: add one more virtual function:

enum DerivedType
{
    One,
    Two,
    ...
};

class base 
{ 
public: 
   ~base(){} 
   virtual void foo() {}
   virtual DerivedType GetType() = 0;
}; 

template <typename T> 
class derived1 : public base 
{ 
   virtual void foo() {};
   virtual DerivedType GetType() { return One; }
}; 

template <typename T> 
class derived2 : public base 
{ 
   virtual void foo() {};
    virtual DerivedType GetType() { return Two; }
}; 

Solution 2: using tag classes:

class Base
{
public:
    virtual ~Base() { }
};

class Derived1Tag
{ };

class Derived2Tag
{ };

template <class T>
class Derived1 : public Base, public Derived1Tag
{ };

template <class T>
class Derived2 : public Base, public Derived2Tag
{ };


int main(int argc, char** argv)
{
    Derived1<int> d1;
    Derived2<int> d2;

    cout << dynamic_cast<Derived1Tag*>((Base*)&d1) << endl;
    cout << dynamic_cast<Derived1Tag*>((Base*)&d2) << endl;

    return 0;
}