C++最接近追溯定义已定义类的超类是什么?

What's the closest thing in C++ to retroactively defining a superclass of a defined class?

本文关键字:定义 超类 是什么 最接近 C++      更新时间:2023-10-16

假设我有这个类

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*s将起作用。然后请实际接受A*作为B*(A&作为B&等)。

C++我们可以用另一种方式做到这一点,即如果 B 是我们无法控制的类,我们可以用class A : public B { ... }执行"子类 a 已知类"操作;我知道C++没有相反的机制——"超类 A 已知类 A 由新类 B "。我的问题是 - 这种机制最接近可实现的近似值是什么?

笔记:

  • 这都是严格的编译时,而不是运行时。
  • class A没有任何变化。我只能修改B的定义和同时知道AB的代码。其他人仍然会使用类A,如果我希望我的代码与他们的代码交互,我也会这样做。
  • 这最好是"可扩展"到多个超类的。所以也许我也有class C { protected: int x; double w; public: void baz(); }也应该表现得像A的超类.

您可以执行以下操作:

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(); }
};

这个想法是在幕后使用类型擦除(即InterfaceInterfacer<T>类)来允许C接受任何可以调用bar的内容,然后您的库将获取类型为C的对象。

我知道C++没有相反的机制 - "超类 A 已知 类">

哦,是的,确实如此:

template <class Superclass>
class Class : public Superclass
{    
};

然后你走了。不用说,一切都在编译时。


如果你有一个无法更改的class A,需要将其插入继承结构中,那么在

template<class Superclass>
class Class : public A, public Superclass
{
};

请注意,dynamic_cast将在给定指针的情况下到达A*指针Superclass*反之亦然。同上Class*指针。此时,您正在接近组合特征概念

普通模板会这样做,编译器会在您错误使用它们时通知您。

而不是

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 和 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接口做某事的类型。

如果不改变类,就无法更改类的行为。实际上,在已经定义了父类之后,实际上没有添加父类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继承了BC

公开继承A将允许摆脱所有委派样板:

class D : public A, public B, public C {
public:
D(const A&);
};

但是,我认为这可能会在不使用B的情况下使用A的代码和使用知道B(因此使用D)的代码之间造成混淆。使用D的代码可以很容易地处理A,但不能反过来'舍入'。

根本不继承A而是使用成员将允许您不复制A来创建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();}
};

但是,如果不知道BD的其他代码也使用共享指针,这可能只是一个选项。

这似乎更像是静态多态性,而不是动态的。 正如@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 的运行时效率。