Как правильно реализовать шаблон фабричного метода в C ++

329

В C ++ есть одна вещь, которая заставляет меня чувствовать себя некомфортно в течение достаточно долгого времени, потому что я, честно говоря, не знаю, как это сделать, хотя это звучит просто:

Как правильно реализовать Factory Method в C ++?

Цель: позволить клиенту создавать экземпляры некоторого объекта, используя фабричные методы вместо конструкторов объекта, без неприемлемых последствий и снижения производительности.

Под «фабричным методом» я подразумеваю как статические фабричные методы внутри объекта или методы, определенные в другом классе, так и глобальные функции. Как правило, «концепция перенаправления обычного способа создания экземпляров класса X куда-либо еще, кроме конструктора».

Позвольте мне просмотреть некоторые возможные ответы, о которых я подумал.


0) Не делайте фабрики, делайте конструкторов.

Это звучит неплохо (и зачастую является лучшим решением), но не является общим средством. Прежде всего, есть случаи, когда построение объекта является достаточно сложной задачей, чтобы оправдать его выделение в другой класс. Но даже если оставить этот факт в стороне, даже для простых объектов, использующих только конструкторы, часто не получится.

Самый простой пример, который я знаю, это 2-D класс Vector. Так просто, но сложно. Я хочу иметь возможность построить его как из декартовых, так и из полярных координат. Очевидно, я не могу сделать:

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. Продолжайте читать дальше.

другой случай: попытка перегрузки двумя непрозрачными определениями типов некоторого 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);
    }
};

Прохладно? Часто так и есть. Но тогда - это заставляет пользователя использовать только динамическое распределение. Статическое распределение - это то, что делает C ++ сложным, но также и то, что часто делает его мощным. Кроме того, я считаю, что существуют некоторые цели (ключевое слово: внедренные), которые не позволяют динамическое размещение. И это не означает, что пользователям этих платформ нравится писать чистый ООП.

В любом случае, оставим философию: в общем случае я не хочу заставлять пользователей фабрики ограничиваться динамическим распределением.


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 с гарантированным разрешением копирования, отсутствие возможности копирования больше не является проблемой для кода выше )

Вывод: создание фабрики путем возврата объекта действительно является решением для некоторых случаев (например, упомянутый ранее двумерный вектор), но все же не является общей заменой конструкторам.


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, хотя название очень похоже, реальные вопросы ИМХО разные.
Петер Тёрёк
2
Хороший дубликат, но текст этого вопроса ценен сам по себе.
dmckee --- котенок экс-модератора
7
Спустя два года после того, как я спросил об этом, у меня есть несколько моментов, которые нужно добавить: 1) Этот вопрос относится к нескольким шаблонам проектирования ([абстрактный] завод, строитель, как вы его называете, я не люблю углубляться в их таксономию). 2) Фактическая проблема, которая обсуждается здесь, это «как четко отделить выделение памяти для объекта от конструкции объекта?».
Кос
1
@ Денис: только если ты этого не сделаешь 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 чтобы избежать затрат на копирование.

Что касается типа размещения, то основной причиной использования фабричного шаблона обычно является полиморфизм. Конструкторы не могут быть виртуальными, и даже если бы они могли, это не имело бы большого смысла. При использовании статического или стекового выделения нельзя создавать объекты полиморфным способом, поскольку компилятору необходимо знать точный размер. Так что работает только с указателями и ссылками. И возврат ссылки из фабрики тоже не работает, потому что хотя объект технически может быть удален по ссылке, он может быть довольно запутанным и подверженным ошибкам, см., Практика возврата справочной переменной C ++ - зло?например. Так что указатели - это единственное, что осталось, и это включает в себя и умные указатели. Другими словами, фабрики наиболее полезны при использовании с динамическим распределением, поэтому вы можете делать такие вещи:

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

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

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

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

Сергей Таченов
источник
21
+1 для декартовых и полярных структур. Как правило, лучше всего создавать классы и структуры, которые непосредственно представляют данные, для которых они предназначены (в отличие от общей структуры Vec). Ваш Factory тоже хороший пример, но ваш пример не показывает, кому принадлежит указатель «a». Если фабрика «f» владеет ею, то она, вероятно, будет уничтожена, когда «f» выйдет из области видимости, но если «f» не владеет ею, важно, чтобы разработчик не забыл освободить эту память, иначе утечка памяти может происходят.
Дэвид Петерсон
1
Конечно, объект может быть удален по ссылке! См. Stackoverflow.com/a/752699/404734 Это, конечно, поднимает вопрос о целесообразности возврата динамической памяти по ссылке из-за проблемы возможного присвоения возвращаемого значения при копировании (вызывающий объект, конечно, мог бы также что-то сделать например, int a = * возвращаетAPoninterToInt () и столкнется с той же проблемой, если будет возвращена динамическая память с покрытием, как для ссылок, но в версии указателя пользователь должен явно разыменовываться вместо того, чтобы просто забыть явно указать ссылку, чтобы быть неправильным) ,
Кайзерлуди
1
@Kaiserludi, хорошая мысль. Я не думал об этом, но это все еще "злой" способ делать вещи. Отредактировал мой ответ, чтобы отразить это.
Сергей Таченов
Как насчет создания различных неполиморфных классов, которые являются неизменяемыми? В таком случае подходит ли фабричный шаблон для использования в C ++?
дааксикс
@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
@ Дау: Да. Но: 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");
mabg
источник
17

Я в основном согласен с принятым ответом, но есть вариант C ++ 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());

Когда я могу использовать это?

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

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

mbrcknl
источник
1
Я широко использовал это при упаковке ресурсов OpenGL. Удаленные конструкторы копий и назначение копий заставляют использовать семантику перемещения. Затем я создал несколько статических фабричных методов для создания каждого типа ресурса. Это было намного более читабельно, чем диспетчеризация OpenGL, основанная на перечислении, которая часто имеет множество избыточных параметров функции в зависимости от переданного перечисления. Это очень полезная модель, удивлен, что ответ не выше.
Fibbles
11

У Локи есть и Фабричный метод, и Абстрактная Фабрика . Оба документа задокументированы (подробно) в Modern C ++ Design Андеем Александреску. Фабричный метод, вероятно, ближе к тому, что вам кажется после, хотя он все же немного отличается (по крайней мере, если память служит, он требует регистрации типа, прежде чем фабрика сможет создавать объекты этого типа).

Джерри Гроб
источник
1
Даже если он устарел (что я оспариваю), он все равно вполне исправен. Я до сих пор использую Фабрику, основанную на MC ++ D, в новом проекте C ++ 14 с большим эффектом! Более того, модели Factory и Singleton являются, вероятно, наименее устаревшими деталями. В то время как кусочки Локи, как Functionи манипуляции с типами, могут быть заменены на std::functionи, <type_traits>и, хотя лямбды, многопоточность, ссылки на значения имеют следствия, которые могут потребовать незначительной доработки, стандартной замены для синглтонов фабрик, как он их описывает, не существует.
металл
5

Я не пытаюсь ответить на все мои вопросы, так как считаю, что они слишком широкие. Просто пара заметок:

бывают случаи, когда построение объекта является достаточно сложной задачей, чтобы оправдать его выделение в другой класс.

Этот класс на самом деле является Строителем , а не Фабрикой.

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

Тогда вы можете сделать так, чтобы ваша фабрика заключила его в умный указатель. Я верю, что таким образом ты сможешь съесть свой торт и съесть его тоже.

Это также устраняет проблемы, связанные с возвратом по стоимости.

Вывод: создание фабрики путем возврата объекта действительно является решением для некоторых случаев (например, упомянутый ранее двумерный вектор), но все же не является общей заменой конструкторам.

На самом деле. Все шаблоны проектирования имеют свои (специфичные для языка) ограничения и недостатки. Рекомендуется использовать их только тогда, когда они помогут вам решить вашу проблему, а не ради них самих.

Если вы после «идеальной» фабричной реализации, ну, удачи.

Петер Тёрёк
источник
Спасибо за ответ! Но не могли бы вы объяснить, как использование умного указателя снимет ограничение динамического размещения? Я не совсем понял эту часть.
Кос
@ Kos, с помощью умных указателей вы можете скрыть распределение / освобождение реального объекта от ваших пользователей. Они видят только инкапсулирующий интеллектуальный указатель, который во внешнем мире ведет себя как статически размещенный объект.
Петер Тёрёк
@ Кос, не в строгом смысле, AFAIR. Вы передаете объект, который нужно обернуть, который вы, вероятно, выделили динамически в какой-то момент. Затем интеллектуальный указатель становится владельцем этого объекта и обеспечивает его надлежащее уничтожение, когда он больше не нужен (время для разных видов интеллектуальных указателей определяется по-разному).
Петер Тёрёк
3

Это мое решение в стиле c ++ 11. Параметр «base» предназначен для базового класса всех подклассов. Создатели, являющиеся объектами std :: function для создания экземпляров подкласса, могут быть привязкой к вашему подклассу «статическая функция-член» create (некоторые аргументы) '. Это может быть не идеально, но работает для меня. И это своего рода «общее» решение.

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();
  }
}
DAG
источник
Выглядит мило для меня. Как бы вы реализовали (может быть, некоторую макро-магию) статическую регистрацию? Просто представьте, что базовый класс - это некоторый класс обслуживания для объектов. Производные классы предоставляют специальный вид обслуживания этим объектам. И вы хотите постепенно добавлять различные виды услуг, добавляя класс, производный от base, для каждого из этих видов услуг.
St0fF
2

Фабричный образец

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

И если ваш компилятор не поддерживает Оптимизацию возвращаемого значения, угробите его, возможно, он вообще не содержит много оптимизаций ...

Матье М.
источник
Можно ли считать это реализацией фабричного шаблона?
Деннис
1
@ Денис: Как вырожденный случай, я бы так подумал. Проблема в Factoryтом, что он довольно универсален и охватывает много вопросов; например, фабрика может добавить аргументы (в зависимости от среды / настройки) или обеспечить некоторое кэширование (связанное с Flyweight / Pools), но эти случаи имеют смысл только в некоторых ситуациях.
Матье М.
Если бы только смена компилятора была бы такой же легкой, как ты это озвучивал :)
rozina
@rozina: :) Хорошо работает в Linux (gcc / clang замечательно совместимы); Я признаю, что Windows все еще относительно закрыта, хотя она должна стать лучше на 64-битной платформе (меньше патентов, если я правильно помню).
Матье М.
И тогда у вас есть целый встроенный мир с некоторыми компиляторами низкого уровня .. :) Я работаю с таким, который не имеет оптимизации возвращаемого значения. Хотелось бы, чтобы все же было. К сожалению, переключение не является возможным в данный момент. Надеемся, что в будущем он будет обновлен, или мы сделаем переход на что-то еще :)
rozina
1

Я знаю, что на этот вопрос ответили 3 года назад, но это может быть то, что вы искали.

Google выпустил пару недель назад библиотеку, позволяющую легко и гибко распределять динамические объекты. Вот оно: http://google-opensource.blogspot.fr/2014/01/introduction-infact-library.html

Флориан Ричу
источник