ダンゞョンタむプからの脱出。 型が動的に決定されるデヌタを凊理したす





SQLク゚リの結果が、あらゆる皮類のフィヌルドタむプぞの無限のタむプ倉換を意味する堎合。 コヌドがboost ::バリアント型によるオヌバヌロヌドの巚倧な列挙を持぀あいたいなロゞックで満たされおいる堎合。 RPCプロトコルに埓っお任意の型の匕数を受け入れる方法がわからない堎合。 次に、C ++では動的型付け゚ミュレヌションメカニズムが必芁です。 拡匵可胜でナヌザヌフレンドリヌで、明確なAPIを䜜成したす。 事前定矩されたタむプのリストを必芁ずせず、基本クラスぞのポむンタヌを䜿甚するこずを匷制しないもの。 このようなメカニズムがありたす-ダブルディスパッチが圹立ちたす



デュアルディスパッチずは䜕か、C ++でそれを正しく、効率的か぀明確に準備する方法を理解するには、たずそれが必芁な理由を説明し、この゜リュヌションぞのすべおの進化の道筋をたどる必芁がありたす。 この説明がなければ、初心者の開発者は読曞の終わりたでに倢䞭になり、経隓豊富な開発者はおそらく自分の団䜓にdrれ、間違った結論を導き出したす。 したがっお、最初から始めたす-動的型付けの基本ずC ++で必芁なものから。



C ++での動的型付け



C ++では、入力は静的であるため、コンパむル段階で暙準操䜜を操䜜する際の゚ラヌを远跡できたす。 原則ずしお、ケヌスの90で、操䜜の結果のタむプ、たたはすべおの可胜な倀の基本クラスのいずれかを事前に知っおいたす。 ただし、操䜜の結果ずしおの倀のタむプが事前にわからず、実行段階で蚈算される問題のクラスがありたす。 兞型的な䟋は、デヌタベヌスぞのク゚リの結果です。SQLク゚リの実行結果ずしお、ク゚リの完了埌に適切な型に展開する必芁がある䞀連の倀を取埗したす。



別の䟋は、RPCプロトコルを介しおリモヌト関数を呌び出した結果です。結果は実行時に認識されたす。特に、RPCプロトコルの䞭間APIを提䟛するずいう䞀般的な問題を解決する堎合、タスクを蚭定する段階で戻り倀のセットを垞に予枬できるずは限りたせん。 実行時に蚈算するのがより䟿利な型システムで機胜する朜圚的に拡匵可胜な機胜、たずえば同じ関数の匕数やSQLク゚リパラメヌタヌに぀いおは、すべお同じこずが圓おはたりたす。これらは䞀般的に䞀般化するのがより䟿利ですが、同時に、䜕らかの方法で保存する必芁がありたす送信したす。



基本クラスずその子孫を䜿甚した叀兞的な゜リュヌションから始めたす。



操䜜の結果ずしおの倀のタむプが事前にわからず、実行段階で蚈算される問題のクラスがありたす。 兞型的な䟋は、デヌタベヌスク゚リの結果です


基本むンタヌフェむスず継承



C ++の基本むンタヌフェむスを介した埓来の゜リュヌションでは、OOPパラダむムの1぀であるポリモヌフィズムを盎接䜿甚したす。 䞀般クラスは区別され、通垞は抜象的で、盞続人でオヌバヌラむドされる倚くのメ゜ッドを導入し、倀盞続人の共通の祖先の型ぞのリンクたたはポむンタを䜿甚しお䜜業を実行したす。

小さな䟋を考えおみたしょう。 倉庫にさたざたな皮類の商品を保管するずいうタスクがあるずしたす。 補品には、名前、倉庫内の補品カテゎリ識別子、および特定の䟡栌を蚭定したす。 このアプロヌチの基本むンタヌフェむスクラスは次のようになりたす。



class IGoods { public: virtual std::string Name() const = 0; virtual int TypeID() const = 0; virtual float Price() const = 0; };
      
      





たずえば、このような商品のカテゎリをお菓子ずしお蚘述する必芁がある堎合、クラスが必芁です。たずえば、次のような特定の関数Name、TypeID、Priceを持぀基本むンタヌフェむスの継承者です。



 class Candies : public IGoods { public: static const int TYPE_ID = 9001; Candies(std::string const& name, float price); virtual std::string Name() const override { return m_name; } virtual int TypeID() const override { return TYPE_ID; } virtual float Price() const override { return m_price; } private: std::string m_name; float m_price; };
      
      





その結果、基本クラスぞの参照のみで動䜜しおいる間、お菓子などのあらゆる皮類の商品で倉庫を埋めるこずができたす。 ぀たり、倉庫は、商品の名前、䟡栌、蚘事を読むだけで、その䞭に䜕が栌玍されおいるかを気にしないので、原則ずしお、盞続人クラスが実際にリンクの埌ろに隠れおいるものを知る必芁はありたせん。



次の利点がありたす。





ただし、欠点は3぀しかありたせんが、無芖するず、C ++クラスのすべおの利点が倱われ、ベヌスむンタヌフェむスクラスぞのポむンタヌぞの䜜業が枛るため、垞に頭痛の皮になりたす。





実際、この叀兞的なアプロヌチでは、ある堎所のコヌドはホラヌ映画のように芋え、通垞のコンストラクタヌではなく、同様のモンスタヌが珟れたす。



 std::deque<std::unique_ptr<IGoods>> goods; std::unique_ptr<IGoods> result = GoodsFactory::Create<Candies>(); goods.push_back(std::move(result));
      
      





コヌド内の別の堎所では、スマヌトポむンタヌを介しおコレクションの芁玠にアクセスするず、恐怖の圢匏が始たりたす。



 std::deque<std::unique_ptr<IGoods>> another(goods.size()); std::transform(goods.begin(), goods.end(), another.begin(), [](std::unique_ptr<IGoods>& element) { return element->Clone(); } );
      
      





これはすべおC ++の最悪の䌝統に芋えるため、ほずんどの堎合、開発者がむンタヌフェむスに眮かれおいたり、オヌプン゜ヌスシステムに衚瀺されおいおも、そのような構成を通垞ず考えるのは驚くこずではありたせん。



C ++ではすべおが本圓にひどく、生成されたコピヌアンドムヌブコンストラクタヌ、割り圓お挔算子、およびその他の人生の喜びを持぀通垞のクラスではできたせんか コンテナクラスのオブゞェクト内の基本クラスぞのポむンタを操䜜するロゞック党䜓をカプセル化できないのはなぜですか はい、䞀般的には䜕もありたせん。



デヌタクラスの継承



今床は、基本クラスのロゞックをわずかに再構築したす。 基本むンタヌフェむスを操䜜するすべおのロゞックを通垞のC ++クラスにたずめたす。 基本むンタヌフェむスクラスは抜象的でなくなり、クラスオブゞェクトはコンストラクタずデストラクタの通垞のロゞックを受け取り、倀をコピヌしお割り圓おるこずができたすが、最も重芁なこずは、マむナスを取り陀くこずで以前のアプロヌチのすべおの利点を倱わないこずです



蚀い換えるず、基本クラスは、その動䜜が基本クラスのデヌタクラスから継承される埌続クラスによっお決定されるクラスの圢匏でいく぀かのデヌタを受け取りたす...玛らわしいように聞こえたすか 次に䟋を芋おみたしょう。すべおが明らかになりたす。



基本クラスは、むンタヌフェむスクラスに䌌たクラスの圢匏でデヌタを受け取りたす。むンタヌフェむスクラスの動䜜は、盞続人のデヌタクラスによっお決定されたす。 継承は2぀ありたす。デヌタクラスも継承されたす


 //      API class object { public: object(); virtual ~object(); virtual bool is_null() const; virtual std::string to_string() const; protected: //    ! class data; private: std::shared_ptr<data> m_data; }; //     API //     #include class object::data { public: data() { } virtual ~data() { } virtual bool is_null() const { return true; } virtual std::string to_string() const { return "null"; } }; //      API //      ,   object class flower : public object { public: flower(std::string const& name); virtual bool is_null() const override; virtual std::string to_string() const override; virtual std::string name() const; virtual void rename(std::string const& name); protected: //    ! class data; }; //     API //     #include class flower::data : public object::data { public: static const std::string FLOWER_UNKNOWN; data() : m_name(FLOWER_UNKNOWN) { } data(std::string const& name) : m_name(name) { } virtual bool is_null() const override { return false; } virtual std::string to_string() const override { return "flower: " + m_name; } virtual std::string name() const { return m_name; } virtual void rename(std::string const& name) { m_name = name; } private: std::string m_name; };
      
      





実際、通垞は盞続人が倚く、通垞は䟝存ラむブラリに衚瀺されたす。 次に、この楜しいデザむンで䜕ができるのかを理解したす。



 object rose = flower("rose"); object none; std::vector<object> garden; garden.push_back(std::move(rose)); garden.push_back(std::move(none)); garden[1] = flower("gladiolus"); std::for_each(garden.begin(), garden.end(), [](object const& element) { std::cout << element.to_string() << std::endl; } );
      
      





APIクラスメ゜ッドの実装は明らかであり、デヌタ操䜜メ゜ッドをプロキシしたす。 祖先のコンストラクタヌはデヌタを䜜成せず、nullポむンタヌを残したす。盞続人のコンストラクタヌは、目的の型のデヌタの盞続人で祖先のポむンタヌを初期化したす。



オブゞェクトクラスの新しい子孫を䜜成しお、文字列に倉換しお倀をチェックするロゞックに蚭定するこずを劚げるものは䜕もありたせん。 たずえば、靎のフィヌチャクラスを遞択できたす。



 class shoes { public: shoes(long long price); virtual bool is_null() const override; virtual std::string to_string() const override; virtual long long price() const; virtual void discount(long long price); protected: class data; };
      
      





shoes ::デヌタクラスは、flower ::デヌタずの類掚によっお蚘述されたす。 確かに、前の䟋から花を䜿っお庭を操䜜するず、面癜い結果が埗られたす。



 garden.push_back(shoes(100000000000LL));
      
      





したがっお、庭に1000億ルヌブルの靎を残すこずができたす。 たた、これらの靎では、䞍泚意で぀たずき、花を分類したすが、基本クラスのむンタヌフェむスを䜿甚した初期アプロヌチでも同じ問題に盎面したす。 庭に花だけがあるこずを意味する堎合、std :: vectorを䜜成したす。 どうやら、コヌドの䜜成者は、花や靎から原子炉や゚ゞプトのピラミッドなど、未知のゎミたで、庭に䜕でも保管するこずに決めたした。



兞型的なロゞックを持぀通垞のC ++クラスを䜿甚した動的型付けの䞖界ぞようこそ。 いいえ クラスをコピヌするず、リンクがコピヌされるだけです。 C ++クラスロゞックずの最新の矛盟を修正したす。



オブゞェクトを倉曎するずきにコピヌする



基本オブゞェクトは、元のむンタヌフェむスがCloneメ゜ッドで実行したこず、぀たり、継承者のコンテンツをコピヌするこずを孊習するずきです。 同時に、コピヌはできる限り控えめにしお、デヌタをできるだけ遅くコピヌする必芁がありたす。 この条件は、オブゞェクトが倧きくなり、そのコピヌが明瀺的たたは暗黙的に集䞭するほど、より重倧になりたす。 ここでは、オブゞェクトのデヌタを倉曎する際のコピヌの原則が圹立ちたす。



C ++での倉曎時コピヌ、たたはコピヌオンラむトCOWは比范的単玔です。たずえば、文字列QStringを含むどこでもCOWが䜿甚されるQtラむブラリは、これらのオブゞェクトを必芁なものにコピヌするコストを削枛したす最小。



アプロヌチの本質は次のずおりです。





定数バヌゞョンのoperator->オヌバヌロヌドは、必芁なメ゜ッドをデヌタクラスで盎接呌び出し、倖郚クラスぞの呌び出しをプロキシするだけです。

挔算子->オヌバヌロヌドの非定数バヌゞョンはもう少し興味深いもので、呌び出しがデヌタを倉曎するこずを意味したす。 したがっお、倉曎可胜なデヌタを参照するこずを確認する必芁がありたす。 デヌタぞのリンクが䞀意でない堎合、぀たり、コピヌを延期しお他の人のデヌタを参照する堎合は、独自のデヌタのコピヌをコピヌし、目的のメ゜ッドを呌び出しお䜜業する必芁がありたす。



C ++の倉曎時のコピヌは、挔算子->カプセル化されたヘルパヌクラスのオヌバヌロヌドにより、比范的簡単です。 const挔算子ず非const挔算子の䞡方のオヌバヌロヌドをオヌバヌロヌドするこずが重芁です


明確にするために、このような䞭間参照型の最も単玔化されたバヌゞョンを取埗したしょう。



 template <class data_type> class copy_on_write { public: copy_on_write(data_type* data) : m_data(data) { } data_type const* operator -> () const { return m_data.get(); } data_type* operator -> () { if (!m_data.unique()) m_data.reset(new data_type(*m_data)); return m_data.get(); } private: std::shared_ptr<data_type> m_data; };
      
      





良い方法では、コピヌプロセス䞭の䟋倖に察しおだけでなく、マルチスレッドアクセスのためにこのクラスを保護する必芁がありたすが、原則ずしお、クラスはC ++でCOWを実装する基本的な考え方を䌝えるのに十分単玔です。 たた、コピヌコンストラクタヌでは、デヌタクラスはデヌタを耇補するための仮想メ゜ッドの呌び出しを意味するこずを考慮する䟡倀がありたす。



あずは、基本クラスオブゞェクトのデヌタストレヌゞを倉曎するだけです。



 class object { ... protected: class data; private: copy_on_write<data> m_data; };
      
      





したがっお、基本クラスず互換性のある盞続人のクラスの初期化、぀たり実際には動的型付けを取埗したす。 さらに、抜象クラスぞのポむンタヌを操䜜したせん。コンストラクタヌ、デストラクタヌ、コピヌ、および割り圓おを備えた通垞のC ++クラスがあり、独自の子孫を䜜成するためにできるだけ簡単です。 唯䞀の耇雑な問題-プロキシメ゜ッドm_data->メ゜ッド匕数に短瞮は、呌び出し自䜓に加えお、スタックトレヌスなどの蚺断情報を保存できるため、呌び出しのシヌケンスを維持しながら゚ラヌの远跡ず䟋倖のスロヌを簡単にできるため、プラスになりたす䟋倖をスロヌしたメ゜ッド。



実際、デヌタを動的に型指定するためのPimplずDoubleのディスパッチアプロヌチのハむブリッドがあり、実行時に型を取埗したす。



実際、動的デヌタ型付けのためのPimplずDoubleディスパッチアプロヌチのハむブリッドを取埗したした


デヌタクラスむンタヌフェむスを実装したすか



デヌタクラスを実装する堎合、Pimplパタヌンで行われおいるように、倖郚クラスのすべおのメ゜ッドを耇補する必芁はありたせん。 デヌタクラスは2぀の䞻なタスクを実行したす。実装のカプセル化の詳现を隠し、倖郚クラスのメ゜ッドの実装でデヌタぞのアクセスを提䟛したす。 get_およびset_メ゜ッドずいく぀かの補助機胜を䜜成し、倖郚クラスのメ゜ッドでデヌタ凊理を盎接実行するだけで十分です。 したがっお、クラスの実装ずカプセル化の詳现を分離したす。


ダむナミックタむピングの䜿甚



そのため、オプションずしおリモヌト関数呌び出しプロトコルがあるずしたす。これは、デヌタベヌスぞのSQLク゚リのパラメヌタヌ化です。 ゚ンドナヌザヌにAPIを提䟛する䞀般的なメカニズムを䜜成する堎合、実行時に匕数のタむプず結果を蚈算したす。これは、ナヌザヌが匕数ずしお枡すものずリモヌト偎から受け取る結果のタむプが事前にわからないためですなぜなら、䞀連の呌び出しでは、次の呌び出しの匕数はしばしば前の呌び出しの結果に基づいおいるためです。



そのような堎合、基本クラスが盞続人のむンタヌフェヌスだけでなく、盞続人デヌタのコンテナでもある堎合、C ++クラスおよびオブゞェクトに関しお動的型付けを必芁ずする機胜を蚘述する機䌚が埗られたす。



SQLク゚リの䟋を考えおみたしょう。 芁求を実行するための匕数のリストは、オブゞェクト型の匕数の任意の数の関数に察しお同じBoost.Preprocessorによっお生成できたす。



 //  SQL-,  db::SqlQuery  db::SqlQuery query("select * from users as u where u.type = $(usertype) and u.registered >= $(datetime) limit 10"); //    operator () db::SqlQueryResult result = query("admin", datetime::today()); //   std::for_each(result.begin(), result.end(), [](db::SqlQueryRow const& row) { //    object login = row["login"]; if (login.is_null()) std::cout << "not specified"; else std::cout << row["login"]; //   if (row["status"] == "deleted") std::cout << " (deleted)"; std::cout << std::endl; } );
      
      





db :: SqlQuery :: operatorぞの匕数ずしお、オブゞェクトの任意のセットを䜿甚できたす。この堎合、型を䞀般型オブゞェクトにキャストするためのテンプレヌト暗黙コンストラクタヌを定矩する必芁がありたす。



 class object { public: template <typename value_type> object(value_type const& value); ... };
      
      





この堎合、integer、boolean、floating、text、datetimeなどの圢匏のオブゞェクトクラスからの継承者が必芁です。オブゞェクトのデヌタは、オブゞェクトが察応する倀で初期化されるずきにオブゞェクトに配眮されたす。 この堎合、オブゞェクトを任意の型で初期化するこずは拡匵可胜であり、オブゞェクトを目的の型に蚭定するために必芁なのは、boolのように、察応する特殊化を蚘述するこずだけです。



 class boolean { public: boolean(bool value) : object(value) { } ... protected: class data; friend class object; }; template<> object::object(bool value) : m_data(new boolean::data(value)) { }
      
      





ここで最も重芁なこずは異なりたす。ク゚リの結果は、実行時にデヌタベヌスのリモヌト偎で蚈算されたデヌタを含むテヌブルです。 ただし、ク゚リ結果のすべおの行を安党に凊理しお、非シリアル化されおいないデヌタではなく、非垞に特定のタむプのオブゞェクトを取埗できたす。 オブゞェクトを操䜜したり、比范操䜜をオヌバヌロヌドしたり、コンストラクタヌずの類掚によっお特定の型の倀を取埗するためのテンプレヌトメ゜ッドを䜜成したり、文字列に持っおきおストリヌムに入れたりするこずができたす。 オブゞェクト型のオブゞェクトは、通垞のクラスオブゞェクトずしお操䜜できるコンテナです。



それだけでなく、必芁に応じお、コンテナのロゞックをオブゞェクトに远加し、リク゚ストから返される倀に察しお1぀のタむプだけを実行できたす。 ぀たり、begin、end、size、およびoperator []メ゜ッドをオヌバヌロヌドしたす。



 object result = query("admin", datetime::today()); std::for_each(result.begin(), result.end(), [](object const& row) { std::for_each(row.begin(), row.end(), [](object const& cell) { std::cout << cell.to_string() << ' '; } std::cout << std::endl; } );
      
      





原則ずしお、コンテナず基本クラスオブゞェクトを介しお䜕でも䜿甚できるようにアむデアを掗緎できたすが、ここでは垞識を忘れおはいけたせん。 コンパむル段階で゚ラヌを明らかにする静的型付けの考え方は非垞に優れおおり、動的型付けを軜率に䜿甚できる堎合はどこでも拒吊するこずは非垞に䞍合理です。



コンパむルの段階で゚ラヌを怜出する静的型付けのアむデアは非垞に優れおおり、それをどこでも攟棄するこずは非垞に䞍合理です


それにもかかわらず、動的型付けは、意図されたたさにその堎所で非垞に有甚です-通垞はデヌタストリヌムの解析の結果ずしお型が動的に取埗される倀に察しお。 さたざたな型のデヌタを操䜜するために基本クラスにカプセル化されたむンタヌフェむスにより、通垞のコンストラクタヌず代入挔算子を䜿甚しおオブゞェクトを䜜成およびコピヌするこずにより、通垞のC ++オブゞェクトを操䜜できたす。コピヌオンラむトテクニックを䜿甚するず、コピヌを可胜な限り理想的には氞久に延期できたす。



同時に、基本クラスは、さたざたなデヌタずコンテナヌを操䜜するためのむンタヌフェむスクラスです。 䟿宜䞊、比范、むンデックス、数孊および論理挔算、フロヌを䜿甚した挔算など、必芁なすべおの挔算を基本クラスに定矩できたす。 䞀般に、基本クラスぞのポむンタヌの保存、さたざたなストリヌムからのコピヌおよびアクセスの゚ラヌから最も保護されおいる、最も読みやすく論理的なコヌドを実装できたす。 これは、このAPIが広範囲のタスク甚に開発され、最初は未知の䞀連のタむプを操䜜するずきに特に圹立ち、䞀連のタむプ自䜓が朜圚的に拡匵される可胜性がありたす。



動的型付けはあなたの責任です



動的な入力を行うずきは、非垞に慎重でなければなりたせん。 スクリプト蚀語の開発者は、アルゎリズムがコンパむル段階で実行される前であっおも、型をチェックするC ++、C、およびJavaのパワヌをしばしばうらやむこずを忘れないでください。 静的型付けの力を䜿甚しお、正圓な堎合にのみ拒吊を゚ミュレヌトしたす 原則ずしお、リモヌトのシリアル化されたデヌタサヌバヌに察する䞀般化されたAPI芁求デヌタベヌス芁求を含むを実行するには、動的な型指定が必芁です。


逆シリアル化の埌、実行時にすでに倚くの型を取埗できたす。 通垞、デヌタを受信するずきに凊理が必芁なため、動的に取埗されたタむプを拒吊し、テキストたたはバむトストリヌムにシリアル化されたデヌタを凊理するこずは正圓化できたせん。 デヌタを解析しお䜿い慣れたC ++型を取埗し、むンタヌフェむスポむンタヌではなく、適切に蚭蚈されたクラスの通垞のオブゞェクトを䜿甚するこずの利䟿性は、貎重です。



新しい方法



, , API C++ , RPC- . , , . , , . copy-on-write operator -> const non-const , , , , . , , , . new — .



, , . , , - , . . , Pimpl, , .



API, C++. , . , , - .



API , . , , — API, .



画像



#189.

: Qualab , ++ Parallels




ハッカヌを賌読する




All Articles