Chapter 1 Memo (1)
1.5 Policies & Policy Classes
Policy Classes
: Determine arbitary but specific small behavior, but different from dynamic run-timeinterface
. EachPolicy class
es are bound to compound user-custom type as template on compile time. Thisclass
type have more importance to syntactic construct that should be valid, rather than which exact functions that class must implement.Host Classes
: That classes that use one or more policies. Host are responsible for assembling the structures and behaviors of their policies in a single complex unit.
1.5.1 Implementing Policy Classes with Template Template Parameters
- テンプレートテンプレートパラメータを使用して、各Policyクラスの継承での特殊化を行うことも出来る。
Template Template Parameter
を使う時には、見た目に騙されずに型パラメータが一致するテンプレートクラスの基本型だけを入れること。
template <template <typename> class CreationPolicy> class WidgetManager : public CreationPolicy<Widget> {}; using MyWidgetMgr = WidgetManager<OpNewCreator>;
- これを使って、同じPolicyを使用して別の型を特殊化したクラスで提供することもできるようになる。
// Library code template <template <typename> class CreationPolicy> class WidgetManager : public CreationPolicy<Widget> { // ... void DoSomething() { auto pW = CreationPolicy<Gadget>().Create(); // ... } };
- Virtual Functionを使用することに似ているが、Policyは静的バインディングを使うので全てがコンパイルに処理される。そしてVtableなどの追加費用も費やさなくても済む。
1.5.2 Implementing Policy Classes with Template Member Functions
struct OpNewCreator { template <class TType> static T* Create() { return new TType(); } };
- Policyを作成するときに、Policyクラスに型引数をつけることではなくて指定したメンバー関数に型引数をつける方法もある。
- しかしメンバー関数に型をつけるようとしたら、コード作成の維持補修が難しくなる。C++03以前のコンパイラーも支援するようにする長所もあるが、今どきとなってはこういうやり方をすすめるのは推薦できなさそう。
1.7 Destructors of Policy Classes
もしHostからPolicyへ型変換をするつもりであるなら、Policyで変換したものは絶対にPolicyのままで削除したりすることは出来ないようにしなければならない。そのため、こういうコードは禁じられている。
MyWidgetManager wm;
PrototypeCreator<Widget>* pCreator = &wm;
delete pCreator;
- 最後の行の実行によるUBを防止する為にはPolicyクラスのDestructorをVirtualにする方法もあるが、そうするとVtableが生じてしまう。
- この本ですすめる方法は、DestructorをProtected以下の権限を持つようにしてPolicy型のインスタンスを削除できないようにコンパイルに任せることである。
template <class T> struct OpNewCreator { static T* Create() { return new T; } protected: ~OpNewCreator() {} };
1.8 Optional Functionality Through Incomplete Instantiation
- クラスTemplateのメンバー関数がコードで使わなかったら、そのメンバー関数はそのコードが特殊化した型によって結果的に正しいコードになるかを確認しない。
例えて、以下のようなコードがあるとする。
template <template <class> class CreationPolicy> class WidgetManager : public CreationPolicy<Widget> { // ... void SwitchPrototype(Widget* pNewPrototype) { CreationPolicy<Widget>& myPolicy = *this; delete myPolicy.GetPrototype(); myPolicy.SetPrototype(pNewPrototype); } };
上記のコードは使い方によって3つの分析結果に分かれる。
- もしCreatorが型をPrototypeを支援する型として特殊化したら、Specializedした型はSwitchPrototype関数を使える。
- もしCreatorが型をPrototypeを支援すない型として特殊化してSwitchPrototype関数を使うとそのSwitchPrototype関数はコンパイラに分析され、そいてコンパイルエラーを吐き出す。
- もしCreatorが型をPrototypeを支援すない型として特殊化してSwitchPrototype関数を使わなかったら、プログラムはコンパイルが通せる。
1.9 Combining Policy Classes
template< class TType, template <class> class CheckingPolicy, template <class> class ThreadingModel> class SmartPtr; using SafeWidgetPtr = SmartPtr<Widget, EnforceNotNull, SingleThreaded>;
- 上のコードのように
Policy
を適切に組み合わせして、モジュール化した効率が良いコードが書けることができる。
1.10 Customizing Structure with Policy Classes
- データを貯蔵するものさえ
Policy
で変換できる。たとえば、SmartPtr
はPointerをセーブするためにあるが、他の実装体ではHandleで処理することもある。
template <class T> class DefaultSmartPtrStorage { public: using PointerType = T*; using ReferenceType = T&; protected: PointerType GetPointer() { return ptr_; } void SetPointer(PointerType ptr) { ptr_ = ptr; } private: PointerType ptr_ = nullptr; };
そしてSmartPtr
はこの様に実装する。
template < typename TType, template <class> class CheckingPolicy, template <class> class ThreadingModel, template <class> class Storage = DefaultSmartPtrStorage> class SmartPtr;
1.11 Compatible and Incompatible Policies
- 一つの
Policy
で派生した様々のPolicy Class
の間にも適切に変換関数を用意しなければいけない。 Policy
のクラス間の変換を効率よくする方法とは、Policy
ごとに変換ができるように実装することである。Policy
のクラス間の変換をよりよくする為には変換するクラスを元としたConstructor
か、Conversionオペレーターを実装する方が望ましい。- だとしても、
Policy
クラス間の変換には適切な政策を考えて実装すべき。
1.12 Decomposing a Class into Policies
- とあるクラスで複数の行動があるとしたら、その行動が複数で処理できるようになればこの行動は
Policy
の適用が出来るデザインを持つ。 typedef
またはusing
を積極的に使うべき。Policy
を実装するときには、他のPolicy
またはサイドエフェクトを起こさないように、即ちお互いに垂直的の実装政策を持つこと。- 垂直的ではない
Policy
政策を実装しようとしたら、そのお互いでの連絡手段を実装しなければいけない。だとしてもそうすることによって複雑度が増しになる。
例を挙げて、Array
というPolicy
があるとする。このポリシーはSmartPtr
が配列を持っているか否かを確かめる為にある。
template <class TType> struct IsArray { TType& ElementAt(TType* ptr, unsigned int index) noexcept { return ptr[index]; } const TType& ElementAt(TType* ptr, unsigned int index) const noexcept { return ptr[index]; } }; template <class TType> struct IsNotArray {};
しかしこのポリシーには問題が潜在する。ポリシーによってdelete[]
をするかdelete
をするかがサイドエフェクトとして存在してしまう。この削除を行うポリシーをDestruction
だとしたら、このArray
とDestruction
はお互いに垂直的でなくなってしまう。
- 結論を言うと、垂直的で
Policy
を作って、そうでなくなければならない場合にはお互いの通信の為にBoolean
整数を利用して伝送しかねない。
Overall
#include <cstdlib> #include <type_traits> #include <memory> struct OPNewCreator { template <class TType> static std::unique_ptr<TType> Create() { return std::unique_ptr<TType>(new TType); } }; struct MallocCreator { template <class TType> static std::unique_ptr<TType> Create() { void* buf = std::malloc(sizeof(TType)); if (buf == nullptr) { return nullptr; } else { return std::unique_ptr<TType>(new(buf) TType); } } }; struct SmartNewCreator { template <typename TType> static std::unique_ptr<TType> Create() { return std::make_unique<TType>(); } }; struct CompileValidityCheck final { template <typename...> struct DetachedParam {}; template <template <typename TParam1> class TPolicy, typename TParam1> struct DetachedParam<TPolicy<TParam1>> { using TParamType1 = TParam1; }; template <typename CompoundCreationPolicy> static constexpr bool IsSatisfiedCreationPolicy() { using TParamType1 = typename DetachedParam<CompoundCreationPolicy>::TParamType1; return false || std::is_same_v<OPNewCreator<TParamType1>,CompoundCreationPolicy> || std::is_same_v<MallocCreator<TParamType1>, CompoundCreationPolicy> || std::is_same_v<SmartNewCreator<TParamType1>, CompoundCreationPolicy>; } }; struct Widget { Widget() = default; int operator()() { return 997; } }; template <class CreationPolicy> class Manager : public CreationPolicy { CompileValidityCheck::DetachedParam<CreationPolicy> TPolicyParam; static_assert( CompileValidityCheck::IsSatisfiedCreationPolicy<CreationPolicy>, R"(Can not create class that not satisfied "creation policy")"); }; int main() { Manager<OPNewCreator<Widget>> widgetManager; auto widget = widgetManager.Create(); const auto returnValue = (*widget)(); return returnValue; }