C++ polymorphic load/save

ぐ巨炮叔叔 提交于 2019-12-23 12:13:36

问题


I'm saving and reloading a bunch of different objects all derived from a common base to a file, and obviously I need to store the class name (or something similar) in order to create the correct object type on reloading.

Saving is easy:

class Base 
{
  virtual string className() const = 0;

  void saveToFile()
  {
    write(className());
    ... other writing stuff
  }
}

class Derived1: public Base 
{
  string className() const { return "Derived1"; };
  ...
}

class Derived2: public Base 
{
  string className() const { return "Derived2"; };
  ...
}

and, loading is easy if you don't mind duplicating the strings...

static Base * Base::factory(const String &cname)
{
  if (cname == "Derived1")
    return new Derived1; 
  else if (cname == "Derived2")
    return = new Derived2; 
  else ...
}

void load()
{
  String cname = readString();

  Base * obj(Base::factory(cname);

  obj->readIt();
}

But, the duplicated strings offends my sense of DRY: Ideally, className() could be static virtual but that isn't allowed. I have a feeling that I'm missing an obvious 'clean' way round this, but I can't see it yet. Any suggestions?

Note: OK, code slightly tweaked using a factory method. Note that this doesn't actually answer the problem!

Note #2: The code above isn't trying to be a perfect representation of the ultimate factory pattern. I'm not concerned with unwanted linkage between base and derived classes, or potential difficulties in extending the hierarchy. I'm looking for answers that will simplify the code rather than complicate it.


回答1:


That's about the best you could do, you might clean it up a bit though by wrapping the if in a factory class.




回答2:


Two things here. First, to avoid having to write out the name twice, I've used something like the following in the past:

class Derived : public Base
{
    //  ...
    static char const* className() { return "Derived"; }
    virtual char const* getClassName() const { return className(); }
                 //  overrides virtual function in the base class...
};

In addition, if you want to be able to read the class from an external source, you'll need some sort of static factory function, which registers itself with a map of such functions. I'd go ahead and do this in Base:

class Base
{
    // ...
protected:
    class Factory
    {
    protected:
        Factory( std::string const& type );
    public:
        virtual Base* constructFromFile( std::istream const& source ) const = 0;
    };
    typedef std::map <std::string, Factory const*> FactoryMap;
    static FactoryMap& factories();

    template <typename Derived>
    class ConcreteFactory : public Factory
    {
    public:
        ConcreteFactory() : Factory( Derived::className() ) {}
        virtual Base* constructFromFile( std::istream const& source ) const
        {
            return new Derived( source );
        }
    };

public:
    static Base* readFromFile( std::istream& source );
};


Base::FactoryMap&
Base::factories()
{
    static FactoryMap theOneAndOnly;
    return theOneAndOnly;
}

Base::Factory::Factory( std::string const& type )
{
    std::pair <FactoryMap::iterator, bool> results 
        = factories().insert( std::make_pair( type, this ) );
    assert (results.second);
}

Base* Base::readFromFile( std::istream& source )
{
    std::string type = readType( source );
    FactoryMap::const_iterator factory = factories().find( type );
    if ( factory == factories().end() ) {
        throw UnknownType(...);
    }
    return factory->second->constructFromFile( std::istream& source );
}

Finally, for each derived class, you'll have to define a constructor taking an std::istream&, and a static instance of Base::ConcreteFactory <Derived>. (As written above, this must be a static member.)




回答3:


You can use a Factory Method (AKA Virtual Constructor) This is explained in the Design Patterns book and in many places throughout the Internet -- you can do a Google search on these terms. There is probably already a discussion of this in StackOverflow.




回答4:


You obviously need the Factory pattern. Read more here http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus




回答5:


Id' make map of strings to functions, that create classes:

std::hash_map<std::string, std::function<Base*()> creators;

then, you can create function to fill map

template <typename T> void add()
{
    creators.insert(std::pair(T::class_name(), []()-> Base* { return new T(); }));
}

usage is simple:

//factory constructor
add<Derived1>();
add<Derived2>();

//creation
Base* r = 0;
auto it = creators.find(string);
if (it != creators.end()) {
    r = (*it)();
}



回答6:


I came across the same problem and found a pretty neat answer in this article: Industrial Strenght Pluggable Factories Close enough to James Kanze's answer, but I suggest you read this and try it yourself.



来源:https://stackoverflow.com/questions/12459112/c-polymorphic-load-save

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!