How to apply overloaded polymorphed function on elements of base class pointer vector

ぐ巨炮叔叔 提交于 2020-04-16 08:15:47

问题


I have a base class Object:

struct Object{
};

and n (in this case 2) classes that inherit from this

struct Integer : public Object{
  int i_;
  Integer(int i) : i_{i}{}
}

struct Float : public Object{
  float f_;
  Float(float f) : f_{f}{}
}

By (ab-)using polymorphism I can now store those two types in a vector:

std::vector<Object*> object_list{new Integer(1), new Float(2.1), new Integer(3), new Float(4.2)};

But now I would like to add all those values together.

I can think of...

1) ...defining functions

Integer* add(Integer* i, Integer* j);
Float*   add(Integer* i, Float* f);
Float*   add(Float* f, Float* g);
Float*   add(Float* f, Integer* i);

But this would require to dynamically cast Object to all available types - twice, which seems like a catastrophe if I have enough children classes.

2) ... Templates, but that won't work, because the types are not known at compile time.

So what is the most efficient way regarding the following requirements:

*Execution time is more important than memory usage (although it should run on an 8GB system)

*It should support an arbitrary number of child classes, but must at least up to 20

*Is not limited to adding, but an arbitrary function f(Object* a, Object* b) should be supported

*The design of the classes is not yet fixed. If something works that requires change (or changing the total structure in it self) that is possible

*All possible types are known upfront, external DLLs do not need to be supported

*Does not need to support multiple inheritance

*Does not need to be robust in error handling. Recoverable would be nice but I can live with a SEGFAULT.


回答1:


using Object = std::variant<Float, Integer>;

now you can have a std::vector<Object> and store Floats and Integers in it.

struct Integer {
  int val = 0;
  friend std::ostream& operator<<( std::ostream& os, Integer const& obj ) {
    return os << obj.val;
  }        
};
struct Float {
  double val = 0.;
  friend std::ostream& operator<<( std::ostream& os, Float const& obj ) {
    return os << obj.val;
  }        
};

using Object = std::variant<Integer, Float>;
std::ostream& operator<<( std::ostream& os, Object const& obj ) {
  // note: if the type in Object doesn't have a << overload,
  // this will recurse and segfault.
  std::visit( [&]( auto const& e ){ os << e; }, obj );
  return os;
}

Integer add_impl(Integer const& i, Integer const& j) { return {i.val + j.val}; }
Float   add_impl(Integer const& i, Float const& j) { return {i.val + j.val}; }
Float   add_impl(Float const& i, Float const& j) { return {i.val + j.val}; }
Float   add_impl(Float const& i, Integer const& j) { return {i.val + j.val}; }

Object  add( Object const& lhs, Object const& rhs ) {
  return std::visit( []( auto& lhs, auto& rhs )->Object { return {add_impl( lhs, rhs )}; }, lhs, rhs );
}

Test code:

Object a = Integer{7};
Object b = Float{3.14};
Object c = Integer{-100};
Object d = Float{0.0};

std::cout << add( a, b ) << "," << add( b, c ) << "," << add( c, d ) << "," << add( add(a, b), add( c, d ) ) << "\n";

this implements a dispatch table (more recent compilers will generate a far more efficient one) that will look for add overloads.

The return type is an Object but it will contain either a Float or an Integer at runtime.

The list of types you support needs to be at one spot, at the definition of Object. These objects don't have to be related types.

You can extend the add_impl in the namespace of the types in Object instead of in a central location. ADL will be used to find the overload set.

Of course, I'd implement operator+ instead of add.

There are some tricks you can use to fix:

// note: if the type in Object doesn't have a << overload,
// this will recurse and segfault.

that problem; basically something like:

namespace ObjectOnly {
  struct Object;
  struct Object:std::variant<Integer, Float> {
    using std::variant<Integer, Float>::variant;
    std::variant<Integer, Float> const& base() const& { return *this; }
    std::variant<Integer, Float> & base()& { return *this; }
    std::variant<Integer, Float> const&& base() const&& { return std::move(*this); }
    std::variant<Integer, Float> && base()&& { return std::move(*this); }
  };
  Object add_impl( Object const& lhs, Object const& rhs ) {
    return std::visit( [](auto& lhs, auto& rhs)->Object { return {lhs+rhs}; }, lhs.base(), rhs.base() );
  }
  Object operator+( Object const& lhs, Object const& rhs ) {
    return add_impl( lhs, rhs );
  }
  std::ostream& stream_impl( std::ostream& os, Object const& obj ) {
    std::visit( [&]( auto const& e ){ os << e; }, obj.base() );
    return os;
  }
  std::ostream& operator<<( std::ostream& os, Object const& obj ) {
    return stream_impl( os, obj );
  }
}

this will block add_impl from being able to see ObjectOnly::operator+. It will still be able to see operator+ in the same namespace as Float or Integer.

See here. If you edit Integer to not support << you'll get a compile-time instead of run-time error.




回答2:


If you can choose a single type as the canonical "common" type, and provide a conversion from polymorphic types to that common type, then you can use that as the final and intermediary result of the sum.

For your example classes, a float object could be used to represent their value:

struct Object{
    operator float() = 0;
};

Then, you can calculate the sum with a loop:

float sum = 0;
for (Object* o : object_list) {
    sum += *o;
}


来源:https://stackoverflow.com/questions/60060462/how-to-apply-overloaded-polymorphed-function-on-elements-of-base-class-pointer-v

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!