Як правильно реалізувати заводський шаблон методу в C ++


329

Є одна річ у С ++, яка змушує мене відчувати себе незручно досить довго, тому що я, чесно кажучи, не знаю, як це зробити, хоча це звучить просто:

Як правильно реалізувати заводський метод у C ++?

Мета: зробити можливим дозволити клієнтові інстанціювати який-небудь об’єкт за допомогою заводських методів замість конструкторів об'єкта без неприпустимих наслідків та досягнень ефективності.

Під "Заводським методом" я маю на увазі як статичні заводські методи всередині об'єкта, або методи, визначені в іншому класі, або глобальні функції. Просто загалом "концепція перенаправлення нормального способу інстанції класу X на інше місце, ніж конструктор".

Дозвольте мені проглянути кілька можливих відповідей, які я придумав.


0) Не робіть фабрик, не робіть конструкторів.

Це звучить приємно (і дійсно часто найкраще рішення), але не є загальним засобом. Перш за все, бувають випадки, коли побудова об'єкта є завданням досить складним, щоб виправдати його вилучення до іншого класу. Але навіть відкладати цей факт убік, навіть для простих об'єктів, що використовують лише конструктори, часто не обійдеться.

Найпростіший приклад, який я знаю, - це 2-D клас вектора. Так просто, але все ж хитро. Я хочу мати можливість побудувати це як з декартових, так і з полярних координат. Очевидно, я не можу:

struct Vec2 {
    Vec2(float x, float y);
    Vec2(float angle, float magnitude); // not a valid overload!
    // ...
};

Мій природний спосіб мислення тоді:

struct Vec2 {
    static Vec2 fromLinear(float x, float y);
    static Vec2 fromPolar(float angle, float magnitude);
    // ...
};

Що замість конструкторів призводить мене до використання статичних заводських методів ... що по суті означає, що я певним чином реалізую заводський зразок ("клас стає власною фабрикою"). Це виглядає добре (і підходить саме для цього конкретного випадку), але в деяких випадках не вдається, що я збираюся описати у пункті 2. Читай далі.

інший випадок: намагаючись перевантажити двома непрозорими typedefs деякого API (наприклад, GUID непов'язаних доменів або GUID та бітфілд), типи семантично абсолютно різні (так - теоретично - дійсні перевантаження), але які фактично виявляються те саме - як неподписані вставки або недійсні покажчики.


1) Шлях Яви

У Java це просто, оскільки у нас є лише динамічно виділені об'єкти. Зробити фабрику так само тривіально, як:

class FooFactory {
    public Foo createFooInSomeWay() {
        // can be a static method as well,
        //  if we don't need the factory to provide its own object semantics
        //  and just serve as a group of methods
        return new Foo(some, args);
    }
}

У програмі C ++ це означає:

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
};

Класно? Часто, справді. Але потім - це змушує користувача використовувати тільки динамічне розподілення. Статичний розподіл - це те, що робить С ++ складним, але також є тим, що часто робить його потужним. Також я вважаю, що існують деякі цілі (ключове слово: вбудовані), які не дозволяють динамічно розподіляти. І це не означає, що користувачі цих платформ люблять писати чистий OOP.

У будь-якому випадку, філософія вбік: У загальному випадку я не хочу змушувати користувачів фабрики стримуватися до динамічного розподілу.


2) Повернення за вартістю

Гаразд, тому ми знаємо, що 1) круто, коли ми хочемо динамічного розподілу. Чому ми не додамо статичного розподілу поверх цього?

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooInSomeWay() {
        return Foo(some, args);
    }
};

Що? Ми не можемо перевантажити тип повернення? О, звичайно, ми не можемо. Тож давайте змінимо назви методів, щоб це відобразити. І так, я написав приклад невірного коду вище, щоб лише підкреслити, наскільки мені не подобається необхідність зміни назви методу, наприклад, тому що ми не можемо правильно реалізувати мовно-агностичний заводський дизайн, оскільки ми маємо змінити назви - і кожен користувач цього коду повинен пам’ятати про відмінність реалізації від специфікації.

class FooFactory {
public:
    Foo* createDynamicFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooObjectInSomeWay() {
        return Foo(some, args);
    }
};

Гаразд ... там у нас це є. Це некрасиво, оскільки нам потрібно змінити назву методу. Це недосконало, оскільки нам потрібно написати один і той же код двічі. Але як тільки зроблено, це працює. Правильно?

Ну зазвичай. Але іноді цього немає. Створюючи Foo, ми фактично залежаємо від компілятора, щоб зробити для нас оптимізацію повернутого значення, оскільки стандарт C ++ є доброзичливим, щоб постачальники компілятора не вказували, коли буде створений об'єкт на місці і коли він буде скопійований при поверненні тимчасовий об'єкт за значенням у C ++. Отже, якщо Foo копіювати дорого, такий підхід ризикований.

А що робити, якщо Foo взагалі не піддається контролю? Ну, а. ( Зауважте, що в C ++ 17 із гарантованим зменшенням копіювання копія, яка не підлягає копіюванню, вже не є проблемою для коду вище )

Висновок: Створення фабрики шляхом повернення об'єкта справді є рішенням для деяких випадків (наприклад, 2-D вектора, який вже згадувався), але все ще не є загальною заміною для конструкторів.


3) Двофазна конструкція

Інша річ, яку хтось, мабуть, придумає - це розділити питання розподілу об'єктів та його ініціалізацію. Зазвичай це призводить до такого коду:

class Foo {
public:
    Foo() {
        // empty or almost empty
    }
    // ...
};

class FooFactory {
public:
    void createFooInSomeWay(Foo& foo, some, args);
};

void clientCode() {
    Foo staticFoo;
    auto_ptr<Foo> dynamicFoo = new Foo();
    FooFactory factory;
    factory.createFooInSomeWay(&staticFoo);
    factory.createFooInSomeWay(&dynamicFoo.get());
    // ...
}

Можна подумати, що це працює як шарм. Єдина ціна, яку ми платимо в нашому коді ...

Оскільки я все це написав і залишив це як останнє, я теж мушу не любити це. :) Чому?

Перш за все ... я щиро не люблю концепцію двофазної конструкції і відчуваю провину, коли її використовую. Якщо я проектую свої об’єкти із твердженням, що "якщо він існує, він знаходиться у дійсному стані", я вважаю, що мій код безпечніший і менш схильний до помилок. Мені це подобається.

Домогтися відмови від цієї конвенції І зміни дизайну мого об'єкта лише для того, щоб зробити з нього фабрику - це, ну, непросто.

Я знаю, що вищесказане не переконає багатьох людей, тому дозвольмо навести ще більш ґрунтовні аргументи. Використовуючи двофазну конструкцію, ви не можете:

  • ініціалізація constабо довідкові змінні члена,
  • передавати аргументи конструкторам базового класу та конструкторам об'єктів-членів.

І, ймовірно, можуть бути ще деякі недоліки, про які я зараз не можу подумати, і я навіть не відчуваю особливого зобов’язання, оскільки перераховані вище пункти кулі вже мене переконують.

Отже: навіть близько до хорошого загального рішення щодо впровадження фабрики.


Висновки:

Ми хочемо створити спосіб об'єкта, який би:

  • допускати рівномірне встановлення даних незалежно від розподілу,
  • давати різні, значущі назви методам побудови (таким чином, не покладаючись на перевантаження аргументами),
  • не вводити значне звернення до продуктивності та, бажано, значне звернення до коду, особливо на стороні клієнта,
  • бути загальним, як і в: можливо ввести для будь-якого класу.

Я вважаю, що я довів, що вказані нами способи не відповідають цим вимогам.

Якісь підказки? Будь ласка, надайте мені рішення, я не хочу думати, що ця мова не дозволить мені належним чином реалізувати таку банальну концепцію.


7
@Zac, хоча назва дуже схожа, фактичні питання IMHO різні.
Péter Török

2
Хороший дублікат, але сам текст цього питання є цінним сам по собі.
dmckee --- кошеня колишнього модератора

7
Через два роки після запитання я маю додати декілька моментів: 1) Це питання стосується кількох моделей дизайну ([реферат] фабрика, будівельник, ви його називаєте, мені не подобається заглиблюватися в їхню систематику). 2) Справжнє питання, що тут обговорюється, - "як чітко відокремити розподіл об'єктів об'єкта від побудови об'єкта?".
Кос

1
@Dennis: тільки якщо ви цього не зробите delete. Такі методи є ідеальними, якщо "документально підтверджено" (вихідний код - це документація ;-)), що абонент приймає право власності на покажчик (читайте: відповідає за видалення його, коли це доречно).
Борис Дальштейн

1
@Boris @Dennis ви також можете зробити це дуже очевидним, повернувши unique_ptr<T>замість T*.
Кос

Відповіді:


107

Перш за все, бувають випадки, коли побудова об'єкта є завданням досить складним, щоб виправдати його вилучення до іншого класу.

Я вважаю, що цей пункт є неправильним. Складність насправді не має значення. Актуальність - це те, що робить. Якщо об’єкт може бути побудований за один крок (не як у схемі будівельника), конструктор - це правильне місце для цього. Якщо вам справді потрібен інший клас для виконання завдання, то це повинен бути клас помічників, який у будь-якому випадку використовується від конструктора.

Vec2(float x, float y);
Vec2(float angle, float magnitude); // not a valid overload!

Для цього є легке рішення:

struct Cartesian {
  inline Cartesian(float x, float y): x(x), y(y) {}
  float x, y;
};
struct Polar {
  inline Polar(float angle, float magnitude): angle(angle), magnitude(magnitude) {}
  float angle, magnitude;
};
Vec2(const Cartesian &cartesian);
Vec2(const Polar &polar);

Єдиним недоліком є ​​те, що він виглядає трохи багатослівним:

Vec2 v2(Vec2::Cartesian(3.0f, 4.0f));

Але добре те, що ви можете відразу побачити, який тип координат ви використовуєте, і в той же час вам не потрібно хвилюватися про копіювання. Якщо ви хочете скопіювати, і це дорого (як це підтверджено, звичайно, профілюванням), ви можете використовувати щось на зразок спільних класів Qt щоб уникнути копіювання накладних даних.

Що стосується типу виділення, то основною причиною використання заводської схеми зазвичай є поліморфізм. Конструктори не можуть бути віртуальними, і навіть якби вони могли, це не мало б сенсу. Під час використання статичного або розподілу стеків ви не можете створювати об'єкти в поліморфному вигляді, оскільки компілятору необхідно знати точний розмір. Так це працює лише з покажчиками та посиланнями. І повернення посилання з фабрики теж не працює, оскільки хоча об'єкт технічно можна видалити за допомогою посилання, він може бути досить заплутаним і схильним до помилок, див .наприклад. Отже, покажчики - це єдине, що залишилося, і це включає також і розумні вказівники. Іншими словами, фабрики найкорисніші при використанні з динамічним розподілом, тому ви можете робити такі дії:

class Abstract {
  public:
    virtual void do() = 0;
};

class Factory {
  public:
    Abstract *create();
};

Factory f;
Abstract *a = f.create();
a->do();

В інших випадках фабрики просто допомагають вирішувати незначні проблеми, як, наприклад, із згаданими вами перевантаженнями. Було б добре, якби можна було використовувати їх рівномірно, але це не сильно шкодить, що це, мабуть, неможливо.


21
+1 для декартових та полярних структур. Як правило, найкраще створювати класи та структури, які безпосередньо представляють дані, для яких вони призначені (на відміну від загальної Vec структури). Ваш завод також є хорошим прикладом, але ваш приклад не показує, кому належить покажчик "a". Якщо фабрика 'f' володіє нею, то вона, ймовірно, буде знищена, коли 'f' залишить область, але якщо 'f' не володіє нею, розробнику важливо пам'ятати, щоб звільнити цю пам'ять, інакше може витік пам'яті трапляються.
Девід Петерсон

1
Звичайно, об’єкт можна видалити за посиланням! Див. Stackoverflow.com/a/752699/404734 Звичайно, виникає питання, чи розумно повернути динамічну пам'ять за посиланням через проблему потенційного присвоєння поверненого значення копією (абонент, звичайно, також може щось зробити як int a = * returnAPoninterToInt (), і тоді вони зіткнуться з тією ж проблемою, якщо динамічна всеохоплена пам'ять повертається, як для довідок, але у версії вказівника користувач повинен явно знеструмити, а не просто забути явно посилатися, щоб помилитися) .
Кайзерлуді

1
@Kaiserludi, приємний момент. Я не думав про це, але все одно це "злий" спосіб робити речі. Відредагував мою відповідь, щоб це відобразити.
Сергій Таченов

А як щодо створення різних неполіморфних класів, незмінних? Чи доцільно використовувати заводський зразок у C ++?
daaxix

@daaxix, навіщо вам потрібна фабрика для створення примірників неполіморфного класу? Я не бачу, що стосується незмінності нічого з цього.
Сергій Таченов

49

Простий заводський приклад:

// Factory returns object and ownership
// Caller responsible for deletion.
#include <memory>
class FactoryReleaseOwnership{
  public:
    std::unique_ptr<Foo> createFooInSomeWay(){
      return std::unique_ptr<Foo>(new Foo(some, args));
    }
};

// Factory retains object ownership
// Thus returning a reference.
#include <boost/ptr_container/ptr_vector.hpp>
class FactoryRetainOwnership{
  boost::ptr_vector<Foo>  myFoo;
  public:
    Foo& createFooInSomeWay(){
      // Must take care that factory last longer than all references.
      // Could make myFoo static so it last as long as the application.
      myFoo.push_back(new Foo(some, args));
      return myFoo.back();
    }
};

2
@LokiAstari Оскільки використання розумних покажчиків - найпростіший спосіб втратити контроль над пам’яттю. Контроль того, які мови C / C ++, як відомо, є вищими порівняно з іншими мовами і в яких вони отримують найбільшу перевагу. Не кажучи вже про те, що розумні вказівники виробляють накладні дані пам'яті, подібні до інших керованих мов. Якщо ви хочете зручності автоматичного управління пам’яттю, починайте програмування на Java або C #, але не вкладайте цей безлад у C / C ++.
luke1985

45
@ lukasz1985 unique_ptrу цьому прикладі не має накладних витрат. Управління ресурсами, включаючи пам'ять, є однією з найважливіших переваг C ++ перед будь-якою іншою мовою, оскільки ви можете це робити без штрафних санкцій та детерміновано, не втрачаючи контролю, але ви говорите прямо навпаки. Деякі люди не люблять те, що C ++ робить неявно, як управління пам'яттю за допомогою розумних покажчиків, але якщо ви хочете, щоб усе було обов'язково явним, використовуйте C; компроміс - це на порядок менше проблем. Я думаю, що це несправедливо, ви проголосуєте гарну рекомендацію.
TheCppZoo

1
@EdMaster: Я раніше не відповідав, тому що він, очевидно, тролінг. Будь ласка, не годуйте троля.
Мартін Йорк

17
@LokiAstari він може бути тролем, але те, що він каже, може бентежити людей
TheCppZoo

1
@yau: Так. Але: boost::ptr_vector<>крихітний трохи ефективніший, оскільки він розуміє, що йому належить покажчик, а не делегування роботи підкласу. Але головна перевага boost::ptr_vector<>полягає в тому, що він виставляє своїх членів за посиланням (а не на вказівник), тому користуватися алгоритмами в стандартній бібліотеці дуже просто.
Мартін Йорк

41

Чи думали ви про те, щоб взагалі не використовувати фабрику, а замість цього добре використовувати типову систему? Я можу придумати два різні підходи, які роблять подібні речі:

Варіант 1:

struct linear {
    linear(float x, float y) : x_(x), y_(y){}
    float x_;
    float y_;
};

struct polar {
    polar(float angle, float magnitude) : angle_(angle),  magnitude_(magnitude) {}
    float angle_;
    float magnitude_;
};


struct Vec2 {
    explicit Vec2(const linear &l) { /* ... */ }
    explicit Vec2(const polar &p) { /* ... */ }
};

Що дозволяє писати такі речі, як:

Vec2 v(linear(1.0, 2.0));

Варіант 2:

ви можете використовувати "теги", як це робить STL з ітераторами тощо. Наприклад:

struct linear_coord_tag linear_coord {}; // declare type and a global
struct polar_coord_tag polar_coord {};

struct Vec2 {
    Vec2(float x, float y, const linear_coord_tag &) { /* ... */ }
    Vec2(float angle, float magnitude, const polar_coord_tag &) { /* ... */ }
};

Цей другий підхід дозволяє писати код, який виглядає приблизно так:

Vec2 v(1.0, 2.0, linear_coord);

що також приємно і виразно, але дозволяє вам мати унікальні прототипи для кожного конструктора.


29

Ви можете прочитати дуже хороше рішення на веб-сайті : http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

Найкраще рішення стосується "коментарів та обговорень", див. "Не потрібно статичних методів створення".

З цієї ідеї я зробив фабрику. Зауважте, що я використовую Qt, але ви можете змінити QMap та QString для std-еквівалентів.

#ifndef FACTORY_H
#define FACTORY_H

#include <QMap>
#include <QString>

template <typename T>
class Factory
{
public:
    template <typename TDerived>
    void registerType(QString name)
    {
        static_assert(std::is_base_of<T, TDerived>::value, "Factory::registerType doesn't accept this type because doesn't derive from base class");
        _createFuncs[name] = &createFunc<TDerived>;
    }

    T* create(QString name) {
        typename QMap<QString,PCreateFunc>::const_iterator it = _createFuncs.find(name);
        if (it != _createFuncs.end()) {
            return it.value()();
        }
        return nullptr;
    }

private:
    template <typename TDerived>
    static T* createFunc()
    {
        return new TDerived();
    }

    typedef T* (*PCreateFunc)();
    QMap<QString,PCreateFunc> _createFuncs;
};

#endif // FACTORY_H

Використання зразка:

Factory<BaseClass> f;
f.registerType<Descendant1>("Descendant1");
f.registerType<Descendant2>("Descendant2");
Descendant1* d1 = static_cast<Descendant1*>(f.create("Descendant1"));
Descendant2* d2 = static_cast<Descendant2*>(f.create("Descendant2"));
BaseClass *b1 = f.create("Descendant1");
BaseClass *b2 = f.create("Descendant2");

17

Я в основному погоджуюся з прийнятою відповіддю, але є варіант С ++ 11, який не був охоплений наявними відповідями:

  • Повернути результати заводських методів за значенням та
  • Забезпечте дешевий конструктор переміщення .

Приклад:

struct sandwich {
  // Factory methods.
  static sandwich ham();
  static sandwich spam();
  // Move constructor.
  sandwich(sandwich &&);
  // etc.
};

Потім ви можете сконструювати об'єкти на стеку:

sandwich mine{sandwich::ham()};

Як суб'єкти інших речей:

auto lunch = std::make_pair(sandwich::spam(), apple{});

Або динамічно виділяється:

auto ptr = std::make_shared<sandwich>(sandwich::ham());

Коли я можу це використати?

Якщо на загальнодоступному конструкторі неможливо дати значущі ініціалізатори для всіх членів класу без попереднього розрахунку, я можу перетворити цей конструктор у статичний метод. Статичний метод виконує попередні обчислення, після чого повертає значення значення через приватний конструктор, який просто робить ініціалізацію, що належить до членів.

Я кажу: " може ", оскільки це залежить від того, який підхід дає найясніший код, не будучи зайвим неефективним.


1
Я широко використовував це під час загортання ресурсів OpenGL. Видалені конструктори копій та призначення копії змушують використовувати семантику переміщення. Потім я створив купу статичних заводських методів для створення кожного типу ресурсів. Це було набагато читабельніше, ніж відправлення на основі перерахунків OpenGL, що часто має купу надмірних функціональних параметрів залежно від кількості переданих перерахунків. Це дуже корисна модель, здивований, що ця відповідь не вище.
Фіблз

11

У Локі є і заводський метод, і абстрактний завод . Обидва вони задокументовані (в значній мірі) в сучасному дизайні C ++ , Андєєм Александреску. Фабричний метод, ймовірно, ближчий до того, що вам здається після, хоча він все-таки дещо інший (принаймні, якщо пам'ять служить, вона вимагає, щоб ви зареєстрували тип, перш ніж фабрика зможе створити об'єкти цього типу).


1
Навіть якщо вона застаріла (що я заперечую), вона все ще ідеально справна. Я все ще використовую Фабрику на основі MC ++ D у новому проекті C ++ 14, щоб досягти великого ефекту! Більше того, фабричні та синглтонські моделі, мабуть, є найменш застарілими частинами. Хоча шматки, подібні до Локі, Functionі маніпуляції з типом можуть бути замінені на, std::functionі в <type_traits>той час, як лямбда, різьблення, рефлекторні рефлекси мають наслідки, які можуть зажадати невеликих налаштувань, не існує стандартної заміни одинакових фабрик, як він їх описує.
метал

5

Я не намагаюся відповісти на всі мої запитання, оскільки вважаю це занадто широким. Всього кілька приміток:

бувають випадки, коли побудова об'єкта є завданням досить складним, щоб обґрунтувати його вилучення до іншого класу.

Цей клас насправді є будівельником , а не фабрикою.

У загальному випадку я не хочу змушувати користувачів фабрики стримуватися до динамічного розподілу.

Тоді ви могли б фабрику інкапсулювати в розумний покажчик. Я вірю, що таким чином ви можете мати свій торт і з'їсти його теж.

Це також усуває проблеми, пов'язані з рентабельністю.

Висновок: Створення фабрики шляхом повернення об'єкта справді є рішенням для деяких випадків (наприклад, 2-D вектора, який вже згадувався), але все ще не є загальною заміною для конструкторів.

Справді. Усі шаблони дизайну мають свої (мовні) обмеження та недоліки. Використовувати їх рекомендується лише тоді, коли вони допоможуть вам вирішити вашу проблему, а не заради них самих.

Якщо ви після «ідеальної» фабричної реалізації, добре, удачі.


Дякую за відповідь! Але ви могли б пояснити, як використання розумного покажчика звільнить обмеження динамічного розподілу? Я не зовсім зрозумів цю частину.
Кос

@Kos, за допомогою смарт-покажчиків ви можете приховати розподіл / угоду розміщення фактичного об’єкта від своїх користувачів. Вони бачать лише інкапсулюючий розумний вказівник, який до зовнішнього світу поводиться як статично виділений об'єкт.
Péter Török

@Kos, не в суворому розумінні, AFAIR. Ви передаєте об'єкт, який потрібно обгорнути, який ви, мабуть, динамічно виділили в якийсь момент. Тоді розумний вказівник бере на себе право власності на нього і забезпечує його належне знищення, коли воно більше не потрібно (час якого визначається по-різному для різних видів розумних покажчиків).
Péter Török

3

Це моє рішення у стилі c ++ 11. Параметр 'base' призначений для базового класу всіх підкласів. творці, об'єкти функції std :: для створення екземплярів підкласу можуть бути прив'язкою до вашого підкласу 'статична функція члена' створити (деякі аргументи) '. Це, можливо, не ідеально, але працює для мене. І це якесь «загальне» рішення.

template <class base, class... params> class factory {
public:
  factory() {}
  factory(const factory &) = delete;
  factory &operator=(const factory &) = delete;

  auto create(const std::string name, params... args) {
    auto key = your_hash_func(name.c_str(), name.size());
    return std::move(create(key, args...));
  }

  auto create(key_t key, params... args) {
    std::unique_ptr<base> obj{creators_[key](args...)};
    return obj;
  }

  void register_creator(const std::string name,
                        std::function<base *(params...)> &&creator) {
    auto key = your_hash_func(name.c_str(), name.size());
    creators_[key] = std::move(creator);
  }

protected:
  std::unordered_map<key_t, std::function<base *(params...)>> creators_;
};

Приклад використання.

class base {
public:
  base(int val) : val_(val) {}

  virtual ~base() { std::cout << "base destroyed\n"; }

protected:
  int val_ = 0;
};

class foo : public base {
public:
  foo(int val) : base(val) { std::cout << "foo " << val << " \n"; }

  static foo *create(int val) { return new foo(val); }

  virtual ~foo() { std::cout << "foo destroyed\n"; }
};

class bar : public base {
public:
  bar(int val) : base(val) { std::cout << "bar " << val << "\n"; }

  static bar *create(int val) { return new bar(val); }

  virtual ~bar() { std::cout << "bar destroyed\n"; }
};

int main() {
  common::factory<base, int> factory;

  auto foo_creator = std::bind(&foo::create, std::placeholders::_1);
  auto bar_creator = std::bind(&bar::create, std::placeholders::_1);

  factory.register_creator("foo", foo_creator);
  factory.register_creator("bar", bar_creator);

  {
    auto foo_obj = std::move(factory.create("foo", 80));
    foo_obj.reset();
  }

  {
    auto bar_obj = std::move(factory.create("bar", 90));
    bar_obj.reset();
  }
}

Мені це приємно виглядає. Як би ви здійснили (можливо, якусь макро-магію) статичну реєстрацію? Уявіть собі, що базовий клас - це якийсь клас обслуговування об'єктів. Похідні класи забезпечують особливий вид обслуговування цих об’єктів. І ви хочете поступово додавати різні види послуг, додаючи клас, похідний від бази для кожного з цих видів послуг.
St0fF

2

Фабричний візерунок

class Point
{
public:
  static Point Cartesian(double x, double y);
private:
};

І якщо ваш компілятор не підтримує оптимізацію зворотного значення, викопайте його, він, ймовірно, зовсім не містить оптимізації ...


Чи справді це можна вважати реалізацією заводського зразка?
Денніс

1
@Dennis: Як вироджений випадок, я би так вважав. Проблема зFactory полягає в тому, що він досить загальний і охоплює багато ґрунту; фабрика може додавати аргументи (залежно від середовища / налаштування) або надати наприклад кешування (пов’язане з Flyweight / Pools), але ці випадки мають сенс лише у деяких ситуаціях.
Маттьє М.

Якби тільки змінити компілятор було б так просто, як ви зробите це звуком :)
rozina

@rozina: :) Він добре працює в Linux (gcc / clang надзвичайно сумісні); Я визнаю, що Windows все ще відносно закрита, хоча це має покращитись на 64-бітній платформі (менше патентів у дорозі, якщо я правильно пам'ятаю).
Матьє М.

І тоді у вас є весь вбудований світ з деякими компіляторами subpar .. :) Я працюю з таким, який не має оптимізації зворотного значення. Я хотів би, щоб це все-таки. На жаль, переключити це наразі не є можливим. Сподіваємось, в майбутньому воно буде оновлено, або ми зробимо перехід на інше :)
rozina

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.