端的に言うと単なる前方宣言を利用したトリックです。
いないと思いますが、期待された方はごめんなさい。
#概要
本記事では、インターフェースB
を引数に取るメソッド
virtual IA::Func(const IB* b) = 0;
の実装について考えます。
このメソッドの実装A::Func(const IB* b) override;
で、IB
のサブクラスB
から利用しているAPI等のハンドルを取得したいとなったとき、
最も手っ取り早いのはstatic_cast<const B*>(b)
でサブクラスへキャストすることでしょう。
コーディング規約など、何らかの理由でこのキャストを回避したい場合を考えてみました。
既存のデザインパターンやイディオムとして存在するかどうか分かりませんでした。
名称や類似パターンをご存じの方是非教えてください。
(何が近いんでしょう?Proxy
?Bridge
?)
#方法
冒頭で述べた通り、前方宣言を使います。
以下のようにします。
struct IB
{
struct ImplHolder; // この二行。名前は適当
virtual const ImplHolder& GetImplHolder() const = 0; //
virtual ~IB() {}
static IB* New();
};
サブクラスのB
から参照できる場所で、前方宣言したIB::ImplHolder
を定義します。
#include "IB.h"
struct B;
struct IB::ImplHolder
{
B& ref;
explicit ImplHolder(B& b) : ref(b) {}
B* operator->() const noexcept { return &ref; } // ポインタのように使えると楽
};
class B : public IB
{
IB::ImplHolder m_holder;
public:
virtual const ImplHolder& GetImplHolder() const override;
uint32_t GetHandle() const;
B();
};
御覧のように、インターフェース側ではインナークラスImplHolder
を定義せず、サブクラス側で定義します。
また、内部にサブクラスへの参照を持つことで、b->GetImplHolder()->GetHandle()
のようにしてサブクラスにアクセスすることができます。
利用する側にとってはインナークラスとその取得メソッドがあること位しか分かりません。
B.h
は非公開にしておきましょう。
実際にサブクラスA
で利用する場合は以下のようになるでしょうか。
void A::Func(const IB* b)
{
std::cout << "A::Func(const IB* b)" << std::endl;
auto impl = b->GetImplHolder();
//auto impl = static_cast<const B*>(b); // キャストを使う場合
auto handle = impl->GetHandle();
std::cout << "handle = " << handle << std::endl;
}
このような小細工を入れておくことで、ダウンキャストを回避しつつ、実際に使う側からはサブクラスA
,B
の存在を隠すことができます。
#include "IA.h"
#include "IB.h"
int main()
{
auto a = IA::New();
auto b = IB::New();
a->Func(b);
delete a;
delete b;
return 0;
}
注意点として、サブクラスへの参照を保持する必要があるため、ポインタ一つ分だけサイズが大きくなってしまいます。
64bitだと8byte分増えます(筆者環境)。
A::A() [4byte]
B::B() [8byte]
A::Func(const IB* b)
B::GetHandle()
handle = 4294967295
A::A() [8byte]
B::B() [16byte]
A::Func(const IB* b)
B::GetHandle()
handle = 4294967295
#プログラムの全体
本記事の説明用プログラムの全体を載せておきます。
#pragma once
struct IB;
struct IA
{
virtual void Func(const IB* b) = 0;
virtual ~IA() {}
static IA* New();
};
#pragma once
struct IB
{
struct ImplHolder; // この二行。名前は適当
virtual const ImplHolder& GetImplHolder() const = 0; //
virtual ~IB() {}
static IB* New();
};
#pragma once
#include "IA.h"
class A : public IA
{
public:
A();
virtual void Func(const IB* b) override;
};
#pragma once
#include "IB.h"
class B;
struct IB::ImplHolder
{
B& ref;
explicit ImplHolder(B& b) : ref(b) {}
B* operator->() const noexcept { return &ref; } // ポインタのように使えると楽
};
class B : public IB
{
IB::ImplHolder m_holder;
public:
virtual const ImplHolder& GetImplHolder() const override;
B();
uint32_t GetHandle() const;
};
#include <iostream>
#include "A.h"
#include "B.h"
A::A()
{
std::cout << "A::A() [" << sizeof(*this) << "byte]" << std::endl;
}
void A::Func(const IB* b)
{
std::cout << "A::Func(const IB* b)" << std::endl;
auto impl = b->GetImplHolder();
//auto impl = static_cast<const B*>(b); // キャストを使う場合
auto handle = impl ->GetHandle();
std::cout << "handle = " << handle << std::endl;
}
IA* IA::New()
{
return new A;
}
#include <iostream>
#include "B.h"
const IB::ImplHolder& B::GetImplHolder() const
{
return m_holder;
}
uint32_t B::GetHandle() const
{
std::cout << "B::GetHandle()" << std::endl;
return ~0; // 適当
}
B::B() : m_holder(*this)
{
std::cout << "B::B() [" << sizeof(*this) << "byte]" << std::endl;
}
IB* IB::New()
{
return new B;
}
#include "IA.h"
#include "IB.h"
int main()
{
auto a = IA::New();
auto b = IB::New();
a->Func(b);
delete a;
delete b;
return 0;
}
#どんな時に使うのか
Vulkan
やDirectX
などを利用したライブラリを構築する際、利用者側に実装を隠したい場合でしょうか。
例えばIDeviceContext::DrawTexture2D(const ITexture2D* pTex, int x, int y) = 0;
を実装したい場合は、
DirectX11
を利用するIDeviceContext
のサブクラスは、ITexture2D
からID3D11ShaderResourceView
なんかを取得したいかと思います。
DX11DeviceContext::DrawTexture2D(const ITexture2D* pTex, int x, int y)
{
auto impl = pTex->GetImplHolder();
auto srv = impl->GetSRV();
m_deviceContext->PSSetShaderResources(0, 1, &srv);
// ...
}
#その他
クラス名以外同じコードなのでマクロにしておくといいんじゃないでしょうか。
#define INTERFACE \
struct ImplHolder; \
virtual const ImplHolder& GetImplHolder() const = 0;\
#define HOLDER(base, derive) \
class derive; \
struct base::ImplHolder \
{ \
derive& ref; \
explicit ImplHolder(derive& b) : ref(b) {} \
\
derive* operator->() const noexcept { return &ref; }\
};
#define DERIVED(type) \
IB::ImplHolder m_holder;\
public: \
virtual const ImplHolder& GetImplHolder() const override { return m_holder; }
// struct IB
// {
// INTERFACE
//
// virtual ~IB() {}
//
// static IB* New();
// };
//
// HOLDER(IB, B)
//
// class B : public IB
// {
// DERIVED(IB)
//
// B(); // m_holder の初期化に注意
// uint32_t GetHandle() const;
// };