假设我有班级
class A {
protected:
int x,y;
double z,w;
public:
void foo();
void bar();
void baz();
};
在我的代码和其他代码中定义和使用。现在,我想写一些可以很好地在A上运行的库,但它实际上更通用,并且可以在以下操作:
class B {
protected:
int y;
double z;
public:
void bar();
};
我确实希望我的库是通用的,所以我定义了一个B类,以及它的API。
我希望能够告诉编译器 - 不是在我不再控制的A的定义中,而是在其他地方,可能在B的定义中:
请注意,请尝试将
B
视为A
的超类。因此,特别是将其放在内存中,这样如果我将A*
重新解释为B*
,我期望B*
的代码就可以正常工作。然后请实际接受A*
作为B*
(A&
作为B&
等。)
在C ++中,我们可以通过另一种方式实现这一点,即如果B是我们无法控制的类,我们就可以执行一个已知类的子类" class A : public B { ... }
的操作;而且我知道C ++没有相反的机制 - "通过新的B类#34;超类化已知的A类。我的问题是 - 这个机制最接近可实现的近似值是什么?
注意:
class A
可能会发生 no 更改。我只能修改B
的定义以及了解A
和B
的代码。其他人仍然会使用班级A
,如果我希望我的代码与他们的互动,我也会这样做。class C { protected: int x; double w; public: void baz(); }
,它的行为应该像A
的超类。 答案 0 :(得分:28)
您可以执行以下操作:
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(); }
};
我们的想法是在封面下使用类型删除(Interface
和Interfacer<T>
类),以允许C
采取任何可以调用的内容bar
1}}然后你的库将采用C
类型的对象。
答案 1 :(得分:15)
我知道C ++没有相反的机制 - “超类已知 类“
哦是的确如此:
template <class Superclass>
class Class : public Superclass
{
};
然后离开你。所有在编译时,不用说。
如果你有class A
无法更改并且需要将其插入继承结构中,那么请使用
template<class Superclass>
class Class : public A, public Superclass
{
};
请注意,dynamic_cast
将获得A*
指针Superclass*
指针,反之亦然。同上Class*
指针。此时,您已接近 Composition , Traits 和 Concepts 。
答案 2 :(得分:5)
普通模板执行此操作,编译器会在您错误地使用它们时通知您。
而不是
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; }
}
你写了
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; }
}
你使用BConsumer1&amp; BConsumer2喜欢
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
您可以使用BSubclass<A>
和BSubclass<B>
作为使用B
界面执行某些操作的类型。
答案 3 :(得分:4)
如果不更改类,就无法更改类的行为。在A
已经定义之后,确实没有添加父类的机制。
我只能修改 B 的定义以及了解A和B 的代码。
您无法更改A
,但可以更改使用A
的代码。因此,您可以使用另一个继承自A
的类(我们称之为B
),而不是使用D
。我认为这是所期望机制中最接近的。
D
可以重用A
作为子对象(可能作为基础),如果有用的话。
这应该最好是“可扩展”到多个超类。
D
可以根据需要继承尽可能多的超类。
演示:
class D : A, public B, public C {
public:
D(const A&);
void foo(){A::foo();}
void bar(){A::bar();}
void baz(){A::baz();}
};
如果只有D
继承了A
和A
,则B
的行为与C
的行为完全相同。
公开继承A
将允许删除所有授权样板:
class D : public A, public B, public C {
public:
D(const A&);
};
但是,我认为有可能在使用A
而不知道B
的代码与使用B
知识的代码之间造成混淆(因此使用{{1} })。使用D
的代码可以轻松处理D
,但不能反过来处理。{/ p>
根本没有继承A
,而是使用会员而不允许您复制A
来创建A
,而是引用现有的:{/ p>
D
这显然有可能导致对象生命周期的错误。这可以通过共享指针来解决:
class D : public B, public C {
A& a;
public:
D(const A&);
void foo(){a.foo();}
void bar(){a.bar();}
void baz(){a.baz();}
};
但是,如果其他不了解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();}
};
或B
的代码也使用共享指针,那么这可能只是一个选项。
答案 4 :(得分:3)
这看起来更像静态多态而非动态。正如@ZdeněkJelínek已经提到过的,你可以使用一个模板来确保在编译时传递正确的接口。
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");
}
这应该做你想要的(即对任何类使用bar),而不必求助于vtable查找,也没有能力修改类。应该通过设置适当的优化标志来内联这种间接级别。换句话说,你将具有直接调用bar的运行时效率。