Снова двойка

Jan 19, 2011 12:07

Автор крылатого выражения "виртуальное удаление подозрительно" выдал очередной эпический текст:
http://habrahabr.ru/blogs/cpp/112123/

В этом тексте много-много букв и кода, но он, к большому сожалению, ни о чем, ибо автор (не самым удачным образом) выступил в роли Кэпа, рассказывающего матерым сишникам что такое полиморфизм и с чем его едят.

Самое интересное, что в попытках довести код решения исходной задачи до совершенства (избавиться от создания объект через switch по enum плюс убрать ненавистное виртуальное удаление), автор, в итоге, довел это решение до состояния, когда оно просто не решает поставленную задачу! Потому что в исходной задаче типом создаваемого объекта можно было управлять в runtime (через тот самый enum), а в финальном варианте кода такой возможности нет в принципе.

Понятно, что enum крайне ограниченное и не очень гибкое решение. Собственно, главная опасность enum (плюс всегда сопутствующего ему switch) -- ситуация, когда для добавления нового значения необходимо править код в нескольких местах. Если switch у нас только в одном месте и он разумных размеров, то как некое компромиссное решение он может вполне себе сгодиться (кстати, исходная задачка, в принципе, как раз такой случай).

То, к чему в итоге должен был прийти автор -- та или иная реализация паттерна фабрика.

К примеру:

Copy Source | Copy HTML
enum CodeGeneratorType
{
    cgtJava,
    cgtCpp,
};

class ICodeGenerator : public Utils::IBasicInterface
{
public:
    virtual std::string Code() =  0;
    virtual std::string Thing() =  0;
    // blah-blah-blah

// + add static CodeGeneratorType Type();
};

// ---------------------------------------------------------
// ICodeGenerator implementations

class JavaCodeGenerator : public ICodeGenerator
{
// ICodeGenerator impl
public:
    std::string Code() { return "Java code"; }
    std::string Thing() { return "Java thing"; }

static CodeGeneratorType Type() { return cgtJava; }
};

class CppCodeGenerator : public ICodeGenerator
{
// ICodeGenerator impl
public:
    std::string Code() { return "C++ code"; }
    std::string Thing() { return "C++ thing"; }

static CodeGeneratorType Type() { return cgtCpp; }
};

// TODO: other types

// ---------------------------------------------------------
// Factory (hide this code from user)

class IGeneratorCreator : public Utils::IBasicInterface
{
public:
    virtual CodeGeneratorType Type() const =  0;
    virtual ICodeGenerator* Create() const =  0;
};

template
class GeneratorCreator : public IGeneratorCreator
{
    CodeGeneratorType m_type;

// IGeneratorCreator impl
private:
    CodeGeneratorType Type() const { return TInstance::Type(); }
    ICodeGenerator* Create() const { return new TInstance(); }
};

class Factory : boost::noncopyable
{
    boost::ptr_vector m_creators; // strange virtual delete here :)

template
    void Register()
    {
        m_creators.push_back( new GeneratorCreator() );
    }

public:

Factory()
    {
        Register();
        Register();
        // TODO: register other types here
    }

// return 0 if fail
    ICodeGenerator* Create(CodeGeneratorType type) const
    {
        // O(n) complex
        for(int i =  0; i < m_creators.size(); ++i)
        {
            if (m_creators[i].Type() == type)
            {
                return m_creators[i].Create();
            }
        }

return  0;
    }

};

// single interface function for factory access
ICodeGenerator* CreateCodeGenerator(CodeGeneratorType type)
{
    static Factory f; // warning: thread unsafe solution!
    return f.Create(type);
}

// ---------------------------------------------------------

class UsageExample
{
    boost::scoped_ptr m_gen; // oh god! virtual delete again!

public:

UsageExample(CodeGeneratorType genType) :
      m_gen( CreateCodeGenerator(genType) )
    {
        ESS_ASSERT(m_gen !=  0);
        std::cout << m_gen->Code() << std::endl;
    }

};

В этом коде я умышленно упростил некоторые моменты:
- singletone в таком виде это не thread-safe решение, лучше так не делать
- понятно, что ключ может быть не enum, а, например, строка
- не лепил лишний код, чтобы прийти к NVI; это не абсолютная догма, не стоит ее делать "на всякий случай", когда реальной потребности в этом нет.

Что еще могу сказать по статье...
"Лирическое отступление" у аффтора вышло крайне неудачным. Пример высосан из пальца -- никто в реальности отладку так не делает, т.к. это приводит к серьезным накладным расходам.
Тот код, к которому автор в итоге пришел, можно было сделать на "шаблонном полиморфизме", ибо полиморфизм в runtime там просто не требуется.
Зацикленность на NVI и фобия виртуального удаления впечатляют. Как я и говорил, автору надо больше кодить, а не писать бесполезные статейки на очевидные даже детям темы.

Upd малость поправил код -- интегрировал enum типа в имплементацию интерфейса.

c++, programming

Previous post Next post
Up