動的メモリ割り当てのないファクトリメソッド

C ++でのファクトリメソッドの古典的な実装には1つの重大な欠点があります。このテンプレートの実装で使用される動的多型には、オブジェクトを動的メモリに配置することが含まれます。 ファクトリメソッドによって作成されたオブジェクトのサイズが大きくないが、頻繁に作成される場合は、生産性に悪影響を与える可能性があります。 これは、まず、 new



演算子が小さなメモリの割り当てにあまり効果的ではないという事実と、第2に、小さなメモリブロックの頻繁な割り当て解除自体が多くのリソースを必要とするという事実によるものです。

この問題を解決するには、動的な多相性を維持し(テンプレートがなければテンプレートを実装することはできません)、同時にスタック上のメモリを割り当てるとよいでしょう。

あなたが私がそれをした方法に興味があるなら、猫へようこそ。







クラシックファクトリメソッドの1つの可能な実装:

 #include <iostream> #include <memory> struct Base { static std::unique_ptr<Base> create(bool x); virtual void f() const = 0; virtual ~Base() { std::cout << "~Base()" << std::endl;} }; struct A: public Base { A() {std::cout << "A()" << std::endl;} virtual void f() const override {std::cout << "A::f\t" << ((size_t)this) << std::endl;} virtual ~A() {std::cout << "~A()" << std::endl;} }; struct B: public Base { B() {std::cout << "B()" << std::endl;} virtual void f() const override {std::cout << "B::f\t" << ((size_t)this) << std::endl;} virtual ~B() {std::cout << "~B()" << std::endl;} }; std::unique_ptr<Base> Base::create(bool x) { if(x) return std::unique_ptr<Base>(new A()); else return std::unique_ptr<Base>(new B()); } int main() { auto p = Base::create(true); p->f(); std::cout << "p addr:\t" << ((size_t)&p) << std::endl; return 0; } // compile & run: // g++ -std=c++11 1.cpp && ./a.out
      
      





出力:

 A() A::f 21336080 p addr: 140733537175632 ~A() ~Base()
      
      





ここでコメントすることは何もないと思います。 アドレス範囲により、作成されたオブジェクトが実際にヒープ上にあることを間接的に確認できます。



次に、動的なメモリ割り当てを取り除きます。

上で述べたように、作成されるオブジェクトのサイズは小さく、メモリオーバーランが小さいため、以下に提案するオプションを使用するとパフォーマンスが向上すると想定しています。

 #include <iostream> #include <memory> struct Base { virtual void f() const = 0; virtual ~Base() { std::cout << "~Base()" << std::endl;} }; struct A: public Base {/* code here */}; struct B: public Base {/* code here */}; class BaseCreator { union U { A a; B b; }; public: BaseCreator(bool x) : _x(x) { if(x) (new(m) A()); else (new(m) B()); } ~BaseCreator() { if(_x) { reinterpret_cast<A*>(m)->A::~A(); } else { reinterpret_cast<B*>(m)->B::~B(); } } Base* operator->() { return reinterpret_cast<Base *>(m); } private: bool _x; unsigned char m[sizeof(U)]; }; int main(int argc, char const *argv[]) { BaseCreator p(true); p->f(); std::cout << "p addr:\t" << ((size_t)&p) << std::endl; return 0; }
      
      





出力:

 A() A::f 140735807769160 p addr: 140735807769160 ~A() ~Base()
      
      





印刷された住所で、はいがわかります。 オブジェクトはスタックに配置されます。

ここでの考え方は非常に単純です。ファクトリメソッドが作成するオブジェクトの結合を取得し、それを使用して最も容量の大きい型のサイズを見つけます。 次に、スタックに必要なサイズunsigned char m[sizeof(U)];



を割り当てますunsigned char m[sizeof(U)];



そして、特別な形式new



使用して、オブジェクトnew(m) A()



ます。

reinterpret_cast<A*>(m)->A::~A();



割り当てられたメモリにあるオブジェクトを正しく破棄します。



原則として、これは停止できましたが、結果のソリューションでは、BaseCreatorクラスで作成されている型に関する情報が3つの場所に存在するという事実が好きではありません。 そして、ファクトリメソッドを使用して別のタイプのオブジェクトを作成する必要がある場合、これら3つの場所すべてを同期的に変更する必要があります。 ただし、エラーが発生した場合、コンパイラは何も言いません。 また、実行時に、エラーがすぐに表示されない場合があります。 そして、2〜3種類ではなく、10〜15種類がある場合、それは災害です。



BaseCreatorクラスを改善してみましょう

 class BaseCreator { union U { A a; B b; }; public: BaseCreator(bool x) { if(x) createObj<A>(); else createObj<B>(); } ~BaseCreator() { deleter(m); } //   BaseCreator(const BaseCreator &) = delete; //   BaseCreator(BaseCreator &&) = default; Base* operator->() { return reinterpret_cast<Base *>(m); } private: typedef void (deleter_t)(void *); template<typename T> void createObj() { new(m) T(); deleter = freeObj<T>; } template<typename T> static void freeObj(void *p) { reinterpret_cast<T*>(p)->T::~T(); } unsigned char m[sizeof(U)]; deleter_t *deleter; };
      
      







したがって、作成されたタイプを追加するときに編集が必要な場所は3つではなく、2つありました。 すでに優れていますが、それでも完璧ではありません。 主な問題は残っています。

この問題を解決するには、組合を取り除く必要があります。 ただし、同時に、提供される可視性と必要なサイズを決定する機能を保持します。



しかし、そのサイズを知るだけでなく、このユニオンにリストされているタイプのオブジェクトを動的に作成できる「スマートユニオン」があった場合はどうでしょうか。 まあ、そして同時に、もちろん、型制御を行使します。



問題ありません! これはC ++です!

 template <typename ...Types> class TypeUnion { public: //     TypeUnion() {}; //   TypeUnion(const TypeUnion &) = delete; //   TypeUnion(TypeUnion &&) = default; ~TypeUnion() { //     -  //  ,   if(deleter) deleter(mem); } //     ""   T //    T          //   args    template <typename T, typename ...Args> void assign(Args&&... args) { //         "" static_assert ( usize, "TypeUnion is empty" ); static_assert ( same_as<T>(), "Type must be present in the types list " ); //      -    //  ,    . if(deleter) deleter(mem); //        //  ,     new(mem) T(std::forward<Args>(args)...); //       deleter = freeMem<T>; } //      ""  template<typename T> T* get() { static_assert ( usize, "TypeUnion is empty" ); assert ( deleter ); // TypeUnion::assign was not called return reinterpret_cast<T*>(mem); } private: //         typedef void (deleter_t)(void *); //      TypeUnion    ? static constexpr size_t max() { return 0; } //      static constexpr size_t max(size_t r0) { return r0; } template <typename ...R> static constexpr size_t max(size_t r0, R... r) { return ( r0 > max(r...) ? r0 : max(r...) ); } // is_same    template <typename T> static constexpr bool same_as() { return max( std::is_same<T, Types>::value... ); } //          template<typename T> static void freeMem(void *p) { reinterpret_cast<T*>(p)->T::~T(); } //          static constexpr size_t usize = max( sizeof(Types)... ); //  ,     unsigned char mem[usize]; deleter_t *deleter = nullptr; };
      
      







これで、BaseCreatorの見た目が良くなりました。

 class BaseCreator { TypeUnion<A, B> obj; public: BaseCreator(bool x) { if(x) obj.assign<A>(); else obj.assign<B>(); } //   BaseCreator(const BaseCreator &) = delete; //   BaseCreator(BaseCreator &&) = default; Base* operator->() { return obj.get<Base>(); } };
      
      





今完璧です。 エントリTypeUnion<A, B> obj



union U {A a; B b;}



union U {A a; B b;}



そして、型の不一致を伴うエラーはコンパイル段階で捕捉されます。



完全なサンプルコード
 #include <iostream> #include <memory> #include <cassert> struct Base { virtual void f() const = 0; virtual ~Base() {std::cout << "~Base()\n";} }; struct A: public Base { A(){std::cout << "A()\n";} virtual void f() const override{std::cout << "A::f\n";} virtual ~A() {std::cout << "~A()\n";} }; struct B: public Base { B(){std::cout << "B()\n";} virtual void f() const override{std::cout << "B::f\n";} virtual ~B() {std::cout << "~B()\n";} size_t i = 0; }; template <typename ...Types> class TypeUnion { public: //     TypeUnion() {}; //   TypeUnion(const TypeUnion &) = delete; //   TypeUnion(TypeUnion &&) = default; ~TypeUnion() { //     -  //  ,   if(deleter) deleter(mem); } //     ""   T //    T          //   args    template <typename T, typename ...Args> void assign(Args&&... args) { //         "" static_assert ( usize, "TypeUnion is empty" ); static_assert ( same_as<T>(), "Type must be present in the types list " ); //      -    //  ,    . if(deleter) deleter(mem); //        //  ,     new(mem) T(std::forward<Args>(args)...); //       deleter = freeMem<T>; } //      ""  template<typename T> T* get() { static_assert ( usize, "TypeUnion is empty" ); assert ( deleter ); // TypeUnion::assign was not called return reinterpret_cast<T*>(mem); } private: //         typedef void (deleter_t)(void *); //      TypeUnion    ? static constexpr size_t max() { return 0; } //      static constexpr size_t max(size_t r0) { return r0; } template <typename ...R> static constexpr size_t max(size_t r0, R... r) { return ( r0 > max(r...) ? r0 : max(r...) ); } // is_same    template <typename T> static constexpr bool same_as() { return max( std::is_same<T, Types>::value... ); } //          template<typename T> static void freeMem(void *p) { reinterpret_cast<T*>(p)->T::~T(); } //          static constexpr size_t usize = max( sizeof(Types)... ); //  ,     unsigned char mem[usize]; deleter_t *deleter = nullptr; }; class BaseCreator { TypeUnion<A, B> obj; public: BaseCreator(bool x) { if(x) obj.assign<A>(); else obj.assign<B>(); } //   BaseCreator(const BaseCreator &) = delete; //   BaseCreator(BaseCreator &&) = default; Base* operator->() { return obj.get<Base>(); } }; int main(int argc, char const *argv[]) { BaseCreator p(false); p->f(); std::cout << "sizeof(BaseCreator):" << sizeof(BaseCreator) << std::endl; std::cout << "sizeof(A):" << sizeof(A) << std::endl; std::cout << "sizeof(B):" << sizeof(B) << std::endl; return 0; } // // clang++ -std=c++11 1.cpp && ./a.out
      
      









気づかなかった熊手はありますか?



ご清聴ありがとうございました!



All Articles