Автор крылатого выражения "виртуальное удаление подозрительно" выдал очередной эпический текст:
http://habrahabr.ru/blogs/cpp/112123/ В этом тексте много-много букв и кода, но он, к большому сожалению, ни о чем, ибо автор (не самым удачным образом) выступил в роли Кэпа, рассказывающего матерым сишникам что такое полиморфизм и с чем его едят.
Самое интересное, что в попытках довести код решения исходной задачи до совершенства (избавиться от создания объект через switch по enum плюс убрать ненавистное виртуальное удаление), автор, в итоге, довел это решение до состояния, когда оно просто не решает поставленную задачу! Потому что в исходной задаче типом создаваемого объекта можно было управлять в runtime (через тот самый enum), а в финальном варианте кода такой возможности нет в принципе.
Понятно, что enum крайне ограниченное и не очень гибкое решение. Собственно, главная опасность enum (плюс всегда сопутствующего ему switch) -- ситуация, когда для добавления нового значения необходимо править код в нескольких местах. Если switch у нас только в одном месте и он разумных размеров, то как некое компромиссное решение он может вполне себе сгодиться (кстати, исходная задачка, в принципе, как раз такой случай).
То, к чему в итоге должен был прийти автор -- та или иная реализация паттерна фабрика.
К примеру:
Copy Source |
Copy HTMLenum 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 типа в имплементацию интерфейса.