クラスがあるとしましょう
_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 { ... }
_を使用して「既知のクラスのサブクラス」操作を実行できます。 C++には逆のメカニズムがないことを知っています-「既知のクラスAを新しいクラスBでスーパークラス化する」。私の質問は-このメカニズムの最も近い達成可能な近似は何ですか?
メモ:
class A
_に変更される可能性があります。 B
の定義と、A
とB
の両方を認識するコードのみを変更できます。他の人々は引き続きクラス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(); }
};
アイデアは型消去を使用することです(つまり、Interface
とInterfacer<T>
クラス)を使用して、C
がbar
を呼び出すことができるすべてのものを取得できるようにし、ライブラリはC
タイプのオブジェクトを取得します。
C++には逆のメカニズムがないことを知っています-「既知のクラスのスーパークラス」
そうそうそう:
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
とB
を継承した場合にのみ動作するC
とまったく同じように動作します。
A
をパブリックに継承すると、すべての委任定型を取り除くことができます。
class D : public A, public B, public C {
public:
D(const A&);
};
ただし、A
を認識せずにB
を使用するコードと、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();}
};
ただし、これはおそらく、B
またはD
を認識しない他のコードも共有ポインターを使用する場合の唯一のオプションです。
これは、動的ではなく静的なポリモーフィズムに似ています。 @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");
}
これは、vtableルックアップに頼ったり、クラスを変更したりする必要なく、希望どおりの動作をする(つまり、任意のクラスにbarを使用する)必要があります。このレベルの間接参照は、適切な最適化フラグを設定してインライン化する必要があります。つまり、barを直接呼び出す場合の実行時の効率が得られます。