问题
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 Float
s and Integer
s 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