Friday, December 7, 2007

С++ фабрики и вопросы экологии

Итак, очередной выпуск журнала "Кодим Вместе".
Предлагаю немного отвлечься от насущных вопросов, и заняться чем-нибудь более медитативным и расслабляющим. Например, всем вместе реализовать известный дивный программистский паттерн - фабрику.
На С++, конечно же, на чем же еще?

Итак, наш первый вариант:
//------------------------------------------------------------------
[Фабрика I] Вариант первый - брутальный

struct MegaClass
{
};
class SomeFactory
{
public:
MegaClass * CreateSmth()
{
return new MegaClass();
}
};
_Winnie C++ Colorizer
Тоже ничего себе способ, довольно часто встречается, особенно среди любителей managed сред. Недостатки способа очевидны -
1. Можно легко допустить, чтобы кто-то прострелил себе ногу -

{
factory.CreateSmth();
} // утечка на пустом месте
Особенно если метод называется CreateMyMegaSuperObjectAndDoTenAnotherActions, принимает N параметров, и, кроме этого, еще делает M неочевидных действий;

2. Нечитабельно - факт передачи владения неочевиден.
//------------------------------------------------------------------
[Фабрик II] Вариант на "мягкую" четверку

#include <memory>
struct MegaClass
{
};
class SomeFactory
{
public:
std::auto_ptr<megaclass> CreateSmth()
{
return std::auto_ptr<megaclass>(new MegaClass()); // он
}
};
Почему на четверку? Ибо налицо факт явного использования расширения C++. Стандарт C++ утверждает, что временный объект пользовательского типа представляет собой modifyable rvalue. В терминах стандарта, modifyable rvalue - это такая специальная сущность, у которой можно вызывать не константные методы, но которую никак нельзя связывать с не константными ссылками.
Т.е:

// пусть есть некоторое fnc
void fnc(A & a) { /* определенно что-то делаем тут*/}

// тогда, для него
A().fnc(); // так делать можно
fnc(A()); // а так - не везде :)
Несложно заметить, что в этом варианте как раз и имеется попытка связывания временного обекта

std::auto_ptr<megaclass>(new MegaClass())

с не константной ссылкой - аргументом оператора присваивания (или конструктора копирования) класса std::auto_ptr

template<class>
auto_ptr(auto_ptr<_other>&amp; _Right);
auto_ptr<_ty>&amp; operator=(auto_ptr<_ty>&amp; _Right);

Утешает только то, что в микрософтских компиляторах это расширение не отключается даже по /Za. А вот другие компиляторы вполне могут воспринимать такие творения более агрессивно:

/*
"ComeauTest.c", line 11: error: class "std::auto_ptr" has no suitable
copy constructor
return std::auto_ptr(new MegaClass());
^
*/

Посему - минус бал за непереносимость.

//------------------------------------------------------------------
[Фабрика III] Вариант - на пятерку

#include <memory>
struct MegaClass
{
};
class SomeFactory
{
public:
void CreateSmth(std::auto_ptr<MegaClass> * pResult)
{
pResult->reset(new MegaClass());
}
};

Этот вариант лишен всех вышеописанных недостатков.
Делайте поменьше вредных выбросов.

3 comments:

_winnie said...

Почему плох вариант с возвратом по значению - непонятно. Кстати, посмотри на std::auto_ptr_ref (напрямую его никто не использует), зачем он нужен ;)

И комо-онлайн всё корректно компилирует.

In strict mode, with -tused, Compile succeeded.


Скорее, плох последний вариант. В частности, вопросами "а что будет если туда передать NULL" и меньшей очевидностью из кода "это фабрика". В то время как код
auto_ptr<T> f();
кричит "ЙА ФАБРЕКО!".
А так же он тупо неудобен, при таком стиле программирования часто приходится заводить временные переменные только ради того, что бы передать на них указатель, создание переменной и её инициализация в две строчки.

Кстати, в варианте "на пятерку" становится проблематичным сделать инициализацию как обычно -

class ModestClass
{
   int i;
   std::auto_ptr<MegaClass> m;

   ModestClass(SomeFactory &f)
      :i(0)
      ,m( ??? )
   {
   }
};

PS.
Tag is not allowed: <pre> - чо ваще за фигня. Не могу нормально отформатировать код.

_winnie said...

>в микрософтских компиляторах это расширение не отключается даже по /Za.

/W4:

warning C4239: nonstandard extension used : 'argument' : conversion from 'T' to 'T &'

Ну и #pragma warning(error) по желанию.

ligen said...

1) насчет "ЙА ФАБРИКО" и затруднений инициализации - согласен на 100%

кста, насчет инициализации, можно вынести вызов и в тело конструктора, нефиг мегалогику в списке инициализации городить )

2) насчет возвращаемого значения - Комо и тот поддерживает сие начиная с версии 4.2.45.2.

Я просто хотел подчеркнуть нестандартность этого действа, бывают, знаешь, такие проекты с повышенными требованиями к переносимости