সি ++ এ সত্তা / উপাদান উপাদানগুলি, আমি কীভাবে আবিষ্কার করতে পারি এবং উপাদানগুলি তৈরি করতে পারি?


37

আমি সি ++ তে একটি সত্তা উপাদান সিস্টেমে কাজ করছি যা আমি আশা করি যে উপাদানগুলির মধ্যে আর্টেমিসের (http://piemaster.net/2011/07/entity-comp घटक-artemis/) স্টাইলটি অনুসরণ করতে পারি বেশিরভাগ ক্ষেত্রে ডেটা ব্যাগ এবং এটি যুক্তিযুক্ত সিস্টেমগুলি। আমি এই পদ্ধতির ডেটা-কেন্দ্রিক-নেসের সুবিধা নেওয়ার এবং কিছু সুন্দর সামগ্রীর সরঞ্জাম তৈরি করার আশা করছি।

যাইহোক, একটি হ্যাম্প আমি যেটি দিয়ে আসছি তা হ'ল কীভাবে কোনও ডেটা ফাইল থেকে কিছু সনাক্তকারী স্ট্রিং বা জিইউইডি নেওয়া যায় এবং এটি কোনও সত্তার জন্য উপাদান তৈরি করতে ব্যবহার করতে হয়। অবশ্যই আমি কেবল একটি বড় পার্স ফাংশন করতে পারি:

Component* ParseComponentType(const std::string &typeName)
{
    if (typeName == "RenderComponent") {
        return new RenderComponent();
    }

    else if (typeName == "TransformComponent") {
        return new TransformComponent();
    }

    else {
        return NULL:
    }
}

তবে এটি সত্যিই কুরুচিপূর্ণ। আমি প্রায়শই উপাদানগুলি সংযোজন এবং সংশোধন করতে চাই এবং আশা করি প্রোটোটাইপিংয়ের উদ্দেশ্যে আপনি লুয়ায় একটি উপাদান এবং সিস্টেমটি প্রয়োগ করতে পারেন এমন একটি স্ক্রিপ্টড কম্পোনেন্ট কম্পোনেন্ট তৈরি করছি। আমি কিছু BaseComponentশ্রেণীর কাছ থেকে উত্তরাধিকারসূত্রে ক্লাস লিখতে সক্ষম হতে চাই , সমস্ত কিছু করার জন্য হয়তো কয়েকটা ম্যাক্রোয় টস করলাম এবং তারপরে রানটাইমের সময় ইনস্ট্যান্টিশনের জন্য ক্লাসটি উপলব্ধ থাকবে।

সি # এবং জাভাতে এটি বেশ সহজবোধ্য হবে, যেহেতু আপনি ক্লাস এবং নির্মাতাদের সন্ধানের জন্য সুন্দর প্রতিচ্ছবি এপিআই পান। তবে, আমি সি ++ এ এটি করছি কারণ আমি সেই ভাষায় আমার দক্ষতা বাড়াতে চাই।

সুতরাং এটি সি ++ এ কীভাবে সম্পন্ন হয়? আমি আরটিটিআই সক্ষম করার বিষয়ে পড়েছি, তবে বেশিরভাগ লোকেরা এ সম্পর্কে সতর্ক রয়েছেন বলে মনে হয়, বিশেষত এমন পরিস্থিতিতে যেখানে আমার কেবল অবজেক্টের ধরণের উপসেটের জন্য এটি প্রয়োজন। যদি একটি কাস্টম আরটিটিআই সিস্টেম আমার সেখানে প্রয়োজন হয় তবে আমি কোথায় লিখতে শিখতে যেতে পারি?


1
বেশিরভাগ অপ্রাসঙ্গিক মন্তব্য: আপনি যদি সি ++ তে দক্ষ হতে চান, তবে স্ট্রিং সম্পর্কিত সি নয়, সি ব্যবহার করুন। এর জন্য দুঃখিত, তবে এটি বলা দরকার ছিল।
ক্রিস

আমি আপনাকে শুনছি, এটি খেলনার উদাহরণ ছিল এবং আমার কাছে স্ট্যান্ড :: স্ট্রিং এপিআই মুখস্থ নেই। । । এখনো!
michael.bartnett

@ বিয়ারসিডিপি আমি আমার উত্তরে একটি বড় আপডেট পোস্ট করেছি। বাস্তবায়ন এখন আরও দৃust় এবং দক্ষ।
পল মানতা

@ পলমন্ত আপনার উত্তর আপডেট করার জন্য অনেক ধন্যবাদ! এটি থেকে শিখতে অনেক ছোট ছোট জিনিস আছে।
michael.bartnett

উত্তর:


36

একটি মন্তব্য:
আর্টেমিস বাস্তবায়ন আকর্ষণীয়। আমি আমার উপাদানগুলিকে "অ্যাট্রিবিউটস" এবং "আচরণ" বলি ব্যতীত আমি অনুরূপ সমাধান নিয়ে এসেছি। ধরণের উপাদান পৃথক করার এই পদ্ধতিটি আমার পক্ষে খুব সুন্দরভাবে কাজ করেছে।

সমাধান সম্পর্কিত:
কোডটি ব্যবহার করা সহজ, তবে আপনি সি ++ এর সাথে অভিজ্ঞ না হলে বাস্তবায়নটি অনুসরণ করা শক্ত হতে পারে। তাই ...

পছন্দসই ইন্টারফেস

আমি যা করেছি তা হ'ল সমস্ত উপাদানগুলির একটি কেন্দ্রীয় ভান্ডার। প্রতিটি উপাদান টাইপ একটি নির্দিষ্ট স্ট্রিং ম্যাপ করা হয় (যা উপাদান নাম প্রতিনিধিত্ব করে)। আপনি সিস্টেমটি এভাবে ব্যবহার করেন:

// Every time you write a new component class you have to register it.
// For that you use the `COMPONENT_REGISTER` macro.
class RenderingComponent : public Component
{
    // Bla, bla
};
COMPONENT_REGISTER(RenderingComponent, "RenderingComponent")

int main()
{
    // To then create an instance of a registered component all you have
    // to do is call the `create` function like so...
    Component* comp = component::create("RenderingComponent");

    // I found that if you have a special `create` function that returns a
    // pointer, it's best to have a corresponding `destroy` function
    // instead of using `delete` directly.
    component::destroy(comp);
}

রুপায়ণ

বাস্তবায়নটি খুব খারাপ নয়, তবে এটি এখনও বেশ জটিল; এটিতে টেমপ্লেট এবং ফাংশন পয়েন্টারগুলির কিছু জ্ঞান প্রয়োজন।

দ্রষ্টব্য: জো র্রেছনিগ মন্তব্যগুলিতে কিছু ভাল পয়েন্ট তৈরি করেছেন, মূলত আমার পূর্ববর্তী বাস্তবায়নটি কোডটি অনুকূলকরণের ক্ষেত্রে সংকলকটি কতটা ভাল তা সম্পর্কে অনেক বেশি অনুমান করেছিল; ইস্যুটি ক্ষতিকারক ছিল না, তবে এটি আমাকে ত্রুটিযুক্ত করেছিল। আমি আরও লক্ষ্য করেছি যে প্রাক্তন COMPONENT_REGISTERম্যাক্রো টেমপ্লেটগুলির সাথে কাজ করে না।

আমি কোডটি পরিবর্তন করেছি এবং এখন এই সমস্ত সমস্যার সমাধান করা উচিত। ম্যাক্রো টেমপ্লেটগুলির সাথে কাজ করে এবং জো উত্থাপিত সমস্যাগুলি মোকাবেলা করেছে: অপ্রয়োজনীয় কোডটিকে অপ্টিমাইজ করা এখন কম্পাইলারদের পক্ষে অনেক সহজ।

কম্পোনেন্ট / component.h

#ifndef COMPONENT_COMPONENT_H
#define COMPONENT_COMPONENT_H

// Standard libraries
#include <string>

// Custom libraries
#include "detail.h"


class Component
{
    // ...
};


namespace component
{
    Component* create(const std::string& name);
    void destroy(const Component* comp);
}

#define COMPONENT_REGISTER(TYPE, NAME)                                        \
    namespace component {                                                     \
    namespace detail {                                                        \
    namespace                                                                 \
    {                                                                         \
        template<class T>                                                     \
        class ComponentRegistration;                                          \
                                                                              \
        template<>                                                            \
        class ComponentRegistration<TYPE>                                     \
        {                                                                     \
            static const ::component::detail::RegistryEntry<TYPE>& reg;       \
        };                                                                    \
                                                                              \
        const ::component::detail::RegistryEntry<TYPE>&                       \
            ComponentRegistration<TYPE>::reg =                                \
                ::component::detail::RegistryEntry<TYPE>::Instance(NAME);     \
    }}}


#endif // COMPONENT_COMPONENT_H

কম্পোনেন্ট / detail.h

#ifndef COMPONENT_DETAIL_H
#define COMPONENT_DETAIL_H

// Standard libraries
#include <map>
#include <string>
#include <utility>

class Component;

namespace component
{
    namespace detail
    {
        typedef Component* (*CreateComponentFunc)();
        typedef std::map<std::string, CreateComponentFunc> ComponentRegistry;

        inline ComponentRegistry& getComponentRegistry()
        {
            static ComponentRegistry reg;
            return reg;
        }

        template<class T>
        Component* createComponent() {
            return new T;
        }

        template<class T>
        struct RegistryEntry
        {
          public:
            static RegistryEntry<T>& Instance(const std::string& name)
            {
                // Because I use a singleton here, even though `COMPONENT_REGISTER`
                // is expanded in multiple translation units, the constructor
                // will only be executed once. Only this cheap `Instance` function
                // (which most likely gets inlined) is executed multiple times.

                static RegistryEntry<T> inst(name);
                return inst;
            }

          private:
            RegistryEntry(const std::string& name)
            {
                ComponentRegistry& reg = getComponentRegistry();
                CreateComponentFunc func = createComponent<T>;

                std::pair<ComponentRegistry::iterator, bool> ret =
                    reg.insert(ComponentRegistry::value_type(name, func));

                if (ret.second == false) {
                    // This means there already is a component registered to
                    // this name. You should handle this error as you see fit.
                }
            }

            RegistryEntry(const RegistryEntry<T>&) = delete; // C++11 feature
            RegistryEntry& operator=(const RegistryEntry<T>&) = delete;
        };

    } // namespace detail

} // namespace component

#endif // COMPONENT_DETAIL_H

কম্পোনেন্ট / component.cpp

// Matching header
#include "component.h"

// Standard libraries
#include <string>

// Custom libraries
#include "detail.h"


Component* component::create(const std::string& name)
{
    detail::ComponentRegistry& reg = detail::getComponentRegistry();
    detail::ComponentRegistry::iterator it = reg.find(name);

    if (it == reg.end()) {
        // This happens when there is no component registered to this
        // name. Here I return a null pointer, but you can handle this
        // error differently if it suits you better.
        return nullptr;
    }

    detail::CreateComponentFunc func = it->second;
    return func();
}

void component::destroy(const Component* comp)
{
    delete comp;
}

লুয়ার সাথে প্রসারিত হচ্ছে

আমার অবশ্যই লক্ষ্য করা উচিত যে কিছুটা কাজের সাথে (এটি খুব বেশি কঠিন নয়), এটি কখনও চিন্তা না করেই সি ++ বা লুয়া উভয় ক্ষেত্রেই সংজ্ঞায়িত উপাদানগুলির সাথে নির্বিঘ্নে কাজ করতে ব্যবহার করা যেতে পারে।


ধন্যবাদ! আপনি ঠিকই বলেছেন, পুরোপুরি বুঝতে আমি সি ++ টেম্পলেটগুলির ব্ল্যাক আর্টে এখনও যথেষ্ট সাবলীল নই। তবে, এক-লাইন ম্যাক্রো হ'ল আমি যা খুঁজছিলাম এবং তার উপরে আমি এটি ব্যবহার করব টেমপ্লেটগুলি আরও গভীরভাবে বুঝতে শুরু করতে।
michael.bartnett

6
আমি সম্মতি দিচ্ছি যে এটি মূলত সঠিক পদ্ধতির তবে দুটি জিনিস যা আমার কাছে আটকে রয়েছে: ১. কেন কেবলমাত্র একটি টেম্প্লেটেড ফাংশন ব্যবহার করবেন না এবং বেরোনোর ​​সময় ফাঁস হয়ে যাওয়া কম্পোনেন্টটাইপআইপল দৃষ্টান্ত তৈরির পরিবর্তে ফাংশন পয়েন্টারগুলির মানচিত্র সংরক্ষণ করবেন না (যদি না আসলে সমস্যা হয় না) আপনি একটি। এসও / ডিএলএল বা যদিও কিছু তৈরি করছেন) 2. উপাদান "স্ট্যাটিক ইনিশিয়ালাইজেশন অর্ডার ফিয়াসকো" এর কারণে উপাদান উপাদানবিজ্ঞানটি ভেঙে যেতে পারে। কম্পোনেন্ট রিজিস্ট্রি তৈরির বিষয়টি নিশ্চিত করতে প্রথমে আপনাকে একটি ফাংশন তৈরি করতে হবে যা কোনও স্থানীয় স্ট্যাটিক ভেরিয়েবলের রেফারেন্স দেয় এবং সরাসরি কন্টেন্টরেজিস্ট্রি ব্যবহার না করে কল করে।
লুকাস

@ লুকাস আহ, আপনি তাদের সম্পর্কে পুরোপুরি ঠিক বলেছেন। আমি সেই অনুযায়ী কোড পরিবর্তন করেছি। আমি মনে করি না যেহেতু আমি আগের কোডটিতে কোনও লিক ছিলাম, যেহেতু আমি ব্যবহার করেছি shared_ptrতবে আপনার পরামর্শটি এখনও ভাল।
পল মানতা

1
@ পল: ঠিক আছে, তবে এটি তাত্ত্বিক নয়, সম্ভাব্য প্রতীক দৃশ্যমানতা ফাঁস / লিঙ্কারের অভিযোগ এড়াতে আপনার কমপক্ষে এটি স্থির করা উচিত। এছাড়াও আপনার মন্তব্য "আপনারা যেমন ফিট দেখতে পান তেমন এই ত্রুটিটি পরিচালনা করা উচিত" এর পরিবর্তে "এটি কোনও ত্রুটি নয়" বলা উচিত।

1
@ পলমন্ত: ফাংশন এবং প্রকারগুলিকে কখনও কখনও ওডিআর "লঙ্ঘন" করার অনুমতি দেওয়া হয় (যেমন আপনি যেমন বলেন, টেম্পলেটগুলি)। তবে এখানে আমরা উদাহরণগুলির বিষয়ে কথা বলছি এবং সেগুলি অবশ্যই সর্বদা ওডিআর অনুসরণ করে। কম্পাইলাররা এই ত্রুটিগুলি একাধিক টিইউতে দেখা দিলে এবং এটির প্রতিবেদন করার প্রয়োজন হয় না (এটি সাধারণত অসম্ভব) এবং সুতরাং আপনি অপরিবর্তিত আচরণের ক্ষেত্রটিতে প্রবেশ করেন। যদি আপনার অবশ্যই আপনার ইন্টারফেস সংজ্ঞা জুড়ে পুরোপুরি ঘাটি ঘটাতে হবে, এটিকে স্থিতিশীল করে অন্ততপক্ষে প্রোগ্রামটিকে সুসংজ্ঞায়িত করে রাখে - তবে কোয়েটের সঠিক ধারণা রয়েছে।

9

দেখে মনে হচ্ছে আপনি যা চান তা একটি কারখানা।

http://en.wikipedia.org/wiki/Factory_method_pattern

আপনি যা করতে পারেন তা হল আপনার বিভিন্ন উপাদানগুলি কারখানার সাথে তাদের নামের সাথে সম্পর্কিত হয় এবং তারপরে আপনার উপাদানগুলি তৈরি করতে কনস্ট্রাক্টর পদ্ধতি স্বাক্ষরের স্ট্রিং আইডেন্টিফায়ারের কিছু মানচিত্র রয়েছে।


1
সুতরাং আমার এখনও কোডের এমন কিছু বিভাগ থাকা দরকার যা আমার Componentক্লাসের সমস্ত সম্পর্কে সচেতন , কলিং ComponentSubclass::RegisterWithFactory(), ডান? এটি আরও গতিশীল এবং স্বয়ংক্রিয়ভাবে এটি সেট করার কোনও উপায় আছে? আমি যে ওয়ার্কফ্লোটি সন্ধান করছি তা হ'ল 1. একটি শ্রেণি লিখুন, কেবল কর্রসন্ডিং শিরোলেখ এবং সিপিপি ফাইলের দিকে তাকিয়ে २. গেমটি পুনরায় সংকলন করুন Start. স্টার্ট লেভেল এডিটর এবং নতুন উপাদান শ্রেণি ব্যবহারের জন্য উপলব্ধ।
michael.bartnett

2
স্বয়ংক্রিয়ভাবে এটি হওয়ার কোনও উপায় নেই। যদিও আপনি এটি প্রতি স্ক্রিপ্ট ভিত্তিতে 1 লাইন ম্যাক্রো কল করতে পারেন। পল এর উত্তর যে কিছুটা যায়।
টেট্রাড

1

আমি কিছুক্ষণের জন্য নির্বাচিত উত্তর থেকে পল মান্টার ডিজাইনের সাথে কাজ করেছি এবং অবশেষে নীচে এই আরও জেনারিক এবং সংক্ষিপ্ত কারখানার বাস্তবায়নে এসেছি যে ভবিষ্যতে এই প্রশ্নে যে কেউ আসবে তার জন্য আমি ভাগ করতে আগ্রহী। এই উদাহরণে, প্রতিটি কারখানার অবজেক্ট Objectবেস ক্লাস থেকে উদ্ভূত :

struct Object {
    virtual ~Object(){}
};

স্থির কারখানার শ্রেণি নিম্নরূপ:

struct Factory {
    // the template used by the macro
    template<class ObjectType>
    struct RegisterObject {
        // passing a vector of strings allows many id's to map to the same sub-type
        RegisterObject(std::vector<std::string> names){
            for (auto name : names){
                objmap[name] = instantiate<ObjectType>;
            }
        }
    };

    // Factory method for creating objects
    static Object* createObject(const std::string& name){
        auto it = objmap.find(name);
        if (it == objmap.end()){
            return nullptr;
        } else {
            return it->second();
        }
    }

    private:
    // ensures the Factory cannot be instantiated
    Factory() = delete;

    // the map from string id's to instantiator functions
    static std::map<std::string, Object*(*)(void)> objmap;

    // templated sub-type instantiator function
    // requires that the sub-type has a parameter-less constructor
    template<class ObjectType>
    static Object* instantiate(){
        return new ObjectType();
    }
};
// pesky outside-class initialization of static member (grumble grumble)
std::map<std::string, Object*(*)(void)> Factory::objmap;

উপ-প্রকারের নিবন্ধনের জন্য ম্যাক্রোটি Objectনিম্নরূপ:

#define RegisterObject(type, ...) \
namespace { \
    ::Factory::RegisterObject<type> register_object_##type({##__VA_ARGS__}); \
}

এখন ব্যবহার নিম্নরূপ:

struct SpecialObject : Object {
    void beSpecial(){}
};
RegisterObject(SpecialObject, "SpecialObject", "Special", "SpecObj");

...

int main(){
    Object* obj1 = Factory::createObject("SpecialObject");
    Object* obj2 = Factory::createObject("SpecObj");
    ...
    if (obj1){
        delete obj1;
    }
    if (obj2){
        delete obj2;
    }
    return 0;
}

উপ-প্রকারে প্রতি স্ট্রিং আইডির জন্য ক্ষমতাটি আমার অ্যাপ্লিকেশনটিতে কার্যকর ছিল তবে উপ-প্রকারের জন্য একক আইডির সীমাবদ্ধতা মোটামুটি সোজা হবে।

আমি আশা করি এটি কার্যকর হয়েছে!


1

@ টিমসট্রাবিঙ্গারের জবাব বন্ধ করে , আমি সি ++ 14 স্ট্যান্ডার্ড ব্যবহার করে একটি ফ্যাক্টরি ক্লাস তৈরি করেছি যা স্বেচ্ছাসেবী সদস্যদের একটি স্বেচ্ছাসেবী যুক্তি দিয়ে সংরক্ষণ করতে পারে । আমার উদাহরণ, টিমের মতো নয়, প্রতি ফাংশনটিতে কেবল একটি নাম / কী লাগবে। টিমের মতো, সঞ্চিত প্রতিটি শ্রেণি একটি বেস শ্রেণি থেকে প্রাপ্ত , খনিটিকে বেস বলে

Base.h

#ifndef BASE_H
#define BASE_H

class Base{
    public:
        virtual ~Base(){}
};

#endif

EX_Factory.h

#ifndef EX_COMPONENT_H
#define EX_COMPONENT_H

#include <string>
#include <map>
#include "Base.h"

struct EX_Factory{
    template<class U, typename... Args>
    static void registerC(const std::string &name){
        registry<Args...>[name] = &create<U>;
    }
    template<typename... Args>
    static Base * createObject(const std::string &key, Args... args){
        auto it = registry<Args...>.find(key);
        if(it == registry<Args...>.end()) return nullptr;
        return it->second(args...);
    }
    private:
        EX_Factory() = delete;
        template<typename... Args>
        static std::map<std::string, Base*(*)(Args...)> registry;

        template<class U, typename... Args>
        static Base* create(Args... args){
            return new U(args...);
        }
};

template<typename... Args>
std::map<std::string, Base*(*)(Args...)> EX_Factory::registry; // Static member declaration.


#endif

main.cpp

#include "EX_Factory.h"
#include <iostream>

using namespace std;

struct derived_1 : public Base{
    derived_1(int i, int j, float f){
        cout << "Derived 1:\t" << i * j + f << endl;
    }
};
struct derived_2 : public Base{
    derived_2(int i, int j){
        cout << "Derived 2:\t" << i + j << endl;
    }
};

int main(){
    EX_Factory::registerC<derived_1, int, int, float>("derived_1"); // Need to include arguments
                                                                    //  when registering classes.
    EX_Factory::registerC<derived_2, int, int>("derived_2");
    derived_1 * d1 = static_cast<derived_1*>(EX_Factory::createObject<int, int, float>("derived_1", 8, 8, 3.0));
    derived_2 * d2 = static_cast<derived_2*>(EX_Factory::createObject<int, int>("derived_2", 3, 3));
    delete d1;
    delete d2;
    return 0;
}

আউটপুট

Derived 1:  67
Derived 2:  6

আমি আশা করি এটি কারখানার নকশা ব্যবহার করতে প্রয়োজনীয় লোকদের সহায়তা করে যার জন্য কোনও পরিচয় নির্বাহকের কাজ করার প্রয়োজন হয় না। এটি মজাদার ডিজাইনিং ছিল, সুতরাং আমি আশা করি এটি তাদের কারখানার নকশাগুলিতে আরও নমনীয়তার প্রয়োজন লোকেদের সহায়তা করে ।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.