问题
Suppose I have the class
class A {
protected:
int x,y;
double z,w;
public:
void foo();
void bar();
void baz();
};
defined and used in my code and the code of others. Now, I want to write some library which could very well operate on A's, but it's actually more general, and would be able to operate on:
class B {
protected:
int y;
double z;
public:
void bar();
};
and I do want my library to be general, so I define a B class and that's what its APIs take.
I would like to be able to tell the compiler - not in the definition of A which I no longer control, but elsewhere, probably in the definition of B:
Look, please try to think of
B
as a superclass ofA
. Thus, in particular, lay it out in memory so that if I reinterpret anA*
as aB*
, my code expectingB*
s would work. And please then actually acceptA*
as aB*
(andA&
as aB&
etc.).
In C++ we can do this the other way, i.e. if B is the class we don't control we can perform a "subclass a known class" operation with class A : public B { ... }
; and I know C++ doesn't have the opposite mechanism - "superclass a known class A by a new class B". My question is - what's the closest achievable approximation of this mechanism?
Notes:
- This is all strictly compile-time, not run-time.
- There can be no changes whatsoever to
class A
. I can only modify the definition ofB
and code that knows about bothA
andB
. Other people will still use classA
, and so will I if I want my code to interact with theirs. - This should preferably be "scalable" to multiple superclasses. So maybe I also have
class C { protected: int x; double w; public: void baz(); }
which should also behave like a superclass ofA
.
回答1:
You can do the following:
class C
{
struct Interface
{
virtual void bar() = 0;
virtual ~Interface(){}
};
template <class T>
struct Interfacer : Interface
{
T t;
Interfacer(T t):t(t){}
void bar() { t.bar(); }
};
std::unique_ptr<Interface> interface;
public:
template <class T>
C(const T & t): interface(new Interfacer<T>(t)){}
void bar() { interface->bar(); }
};
The idea is to use type-erasure (that's the Interface
and Interfacer<T>
classes) under the covers to allow C
to take anything that you can call bar
on and then your library will take objects of type C
.
回答2:
I know C++ doesn't have the opposite mechanism - "superclass a known class"
Oh yes it does:
template <class Superclass>
class Class : public Superclass
{
};
and off you go. All at compile time, needless to say.
If you have a class A
that can't be changed and need to slot it into an inheritance structure, then use something on the lines of
template<class Superclass>
class Class : public A, public Superclass
{
};
Note that dynamic_cast
will reach A*
pointers given Superclass*
pointers and vice-versa. Ditto Class*
pointers. At this point, you're getting close to Composition, Traits, and Concepts.
回答3:
Normal templates do this, and the compiler will inform you when you use them incorrectly.
instead of
void BConsumer1(std::vector<B*> bs)
{ std::for_each(bs.begin(), bs.end(), &B::bar); }
void BConsumer2(B& b)
{ b.bar(); }
class BSubclass : public B
{
double xplusz() const { return B::x + B::z; }
}
you write
template<typename Blike>
void BConsumer1(std::vector<Blike*> bs)
{ std::for_each(bs.begin(), bs.end(), &Blike::bar); }
template<typename Blike>
void BConsumer2(Blike& b)
{ b.bar(); }
template<typename Blike>
class BSubclass : public Blike
{
double xplusz() const { return Blike::x + Blike::z; }
}
And you use BConsumer1 & BConsumer2 like
std::vector<A*> as = /* some As */
BConsumer1(as); // deduces to BConsumer1<A>
A a;
BConsumer2(a); // deduces to BConsumer2<A>
std::vector<B*> bs = /* some Bs */
BConsumer1(bs); // deduces to BConsumer1<B>
// etc
And you would have BSubclass<A>
and BSubclass<B>
, as types that use the B
interface to do something.
回答4:
There is no way to change the behaviour of a class without changing the class. There is indeed no mechanism for adding a parent class after A
has already been defined.
I can only modify the definition of B and code that knows about both A and B.
You cannot change A
, but you can change the code that uses A
. So you could, instead of using A
, simply use another class that does inherit from B
(let us call it D
). I think this is the closest achievable of the desired mechanism.
D
can re-use A
as a sub-object (possibly as a base) if that is useful.
This should preferably be "scalable" to multiple superclasses.
D
can inherit as many super-classes as you need it to.
A demo:
class D : A, public B, public C {
public:
D(const A&);
void foo(){A::foo();}
void bar(){A::bar();}
void baz(){A::baz();}
};
Now D
behaves exactly as A
would behave if only A
had inherited B
and C
.
Inheriting A
publicly would allow getting rid of all the delegation boilerplate:
class D : public A, public B, public C {
public:
D(const A&);
};
However, I think that could have potential to create confusion between code that uses A
without knowledge of B
and code that uses knows of B
(and therefore uses D
). The code that uses D
can easily deal with A
, but not the other way 'round.
Not inheriting A
at all but using a member instead would allow you to not copy A
to create D
, but instead refer to an existing one:
class D : public B, public C {
A& a;
public:
D(const A&);
void foo(){a.foo();}
void bar(){a.bar();}
void baz(){a.baz();}
};
This obviously has potential to mistakes with object lifetimes. That could be solved with shared pointers:
class D : public B, public C {
std::shared_ptr<A> a;
public:
D(const std::shared_ptr<A>&);
void foo(){a->foo();}
void bar(){a->bar();}
void baz(){a->baz();}
};
However, this is presumably only an option if the other code that doesn't know about B
or D
also uses shared pointers.
回答5:
This seems more like static polymorphism rather dynamic. As @ZdeněkJelínek has already mentioned, you could you a template to ensure the proper interface is passed in, all during compile-time.
namespace details_ {
template<class T, class=void>
struct has_bar : std::false_type {};
template<class T>
struct has_bar<T, std::void_t<decltype(std::declval<T>().bar())>> : std::true_type {};
}
template<class T>
constexpr bool has_bar = details_::has_bar<T>::value;
template<class T>
std::enable_if_t<has_bar<T>> use_bar(T *t) { t->bar(); }
template<class T>
std::enable_if_t<!has_bar<T>> use_bar(T *) {
static_assert(false, "Cannot use bar if class does not have a bar member function");
}
This should do what you'd like (i.e. use bar for any class) without having to resort to a vtable lookup and without having the ability to modify classes. This level of indirection should be inlined out with proper optimization flags set. In other words you'll have the runtime efficiency of directly invoking bar.
来源:https://stackoverflow.com/questions/44329583/whats-the-closest-thing-in-c-to-retroactively-defining-a-superclass-of-a-defi