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

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

Как правильно реализовать фабричный метод в 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

В 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 или ссылочные переменные-члены,
  • передать аргументы конструкторам базового класса и конструкторам объектов-членов.

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

Итак: даже близко к хорошему общему решению для реализации фабрики.


Выводы:

Мы хотим иметь способ создания экземпляра объекта, который:

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

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

Есть подсказки? Пожалуйста, дайте мне решение, я не хочу думать, что этот язык не позволит мне должным образом реализовать такую ​​тривиальную концепцию.


person Kos    schedule 25.02.2011    source источник
comment
дубликат шаблона stackoverflow.com/questions / 4992307 /   -  person Zac Howland    schedule 25.02.2011
comment
@Zac, хотя название очень похоже, собственно вопросы ИМХО разные.   -  person Péter Török    schedule 25.02.2011
comment
Хороший дубликат, но текст этого вопроса ценен сам по себе.   -  person dmckee --- ex-moderator kitten    schedule 25.02.2011
comment
Не хотите ли вы частный конструктор? Обдумывая эту проблему некоторое время назад, я подумал, что лучший способ инкапсулировать создание объекта - это использовать отдельный класс менеджера, который был другом частного конструктора. Это заставит любой внешний объект использовать фабричные функции, принадлежащие менеджеру.   -  person Dennis    schedule 25.02.2011
comment
@ sellibitze- нет, давай, ты такая соответствующая марка автомобилей, почему я не могу найти ни одной машины, которая не сломалась бы? везде продают хорошие машины, и я жду того же от вас.   -  person Kos    schedule 25.02.2011
comment
Я думаю, ваш первый пример действительно описывает идиому именованного конструктора, а не фабричный шаблон (en .wikibooks.org / wiki / More_C% 2B% 2B_Idioms / Named_Constructor)   -  person Evan Teran    schedule 25.02.2011
comment
@Peter, @dmckee: Я согласен с тем, что этот вопрос более подробный, но, тем не менее, это тот же вопрос: как правильно использовать шаблон проектирования фабрики в C ++?   -  person Zac Howland    schedule 25.02.2011
comment
@Zac: Суть моего комментария заключается в том, что этот вопрос должен оставаться не удаленным и не объединенным, даже если он закрыт как дубликат.   -  person dmckee --- ex-moderator kitten    schedule 25.02.2011
comment
Умм, разве вы не хотели сказать: Foo * FooFactory :: createDynamicFooInSomeWay (); Foo FooFactory :: createStaticFooInSomeWay ()   -  person Krazy Glew    schedule 20.12.2012
comment
Я взял на себя смелость изменить пример в исходном вопросе. Я собирался вызвать второй createStaticFooInSomeWay (), но это было неправильно - это больше похоже на createDynamicallyAllocatedCFooInSomeWayAndReturnPtr () и createFooValueAndReturnCopy (), но они слишком многословны. (Если есть стандартное соглашение об именах для такого рода вещей, я бы хотел услышать об этом.)   -  person Krazy Glew    schedule 20.12.2012
comment
@KrazyGlew Спасибо за редактирование, это хороший улов.   -  person Kos    schedule 20.12.2012
comment
Спустя два года после того, как я задал этот вопрос, я хочу добавить несколько моментов: 1) Этот вопрос относится к нескольким шаблонам проектирования ([абстрактная] фабрика, строитель, вы называете это, я не люблю вникать в их таксономия). 2) Фактическая проблема, обсуждаемая здесь, заключается в том, как четко отделить выделение памяти объекта от создания объекта ?.   -  person Kos    schedule 20.12.2012
comment
Эм, вы в параграфе 2) не описываете static распределение, это распределение стека, то есть локальное, то есть auto в C ++   -  person ThomasMcLeod    schedule 07.06.2013
comment
Не создаст ли вариант динамического выделения, который вы даете в C ++, утечку памяти?   -  person Dennis    schedule 01.07.2013
comment
@ Деннис: только если ты не delete. Такого рода методы вполне подходят, если документировано (исходный код - это документация ;-)), что вызывающий объект становится владельцем указателя (читайте: отвечает за его удаление, когда это необходимо).   -  person Boris Dalstein    schedule 15.07.2013
comment
@Boris @Dennis, вы также можете сделать это очень явным, вернув unique_ptr<T> вместо T*.   -  person Kos    schedule 30.01.2014
comment
Я не понимаю, в чем смысл фабрики, когда вы не используете указатели: если вы не используете указатели (и, следовательно, полиморфизм), тогда вы должны знать конкретный тип при написании кода. Так зачем же тогда завод? И если вы думаете о вставке объекта (стека) в указатель, ну, это немного сложно, и я не вижу, где / когда это может быть удобно (хотя, никогда не говори никогда).   -  person bartgol    schedule 29.07.2014
comment
@bartgol Я оказался в этом положении, потому что хочу провести модульное тестирование класса, который создает объекты внутри некоторых своих методов, а у меня нет кучи. Поэтому мне нужно распределение стека, и мне нужно иметь возможность изменять типы в зависимости от того, является ли это производственным или тестовым кодом.   -  person dwanderson    schedule 20.11.2015


Ответы (10)


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

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

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();

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

person Sergei Tachenov    schedule 25.02.2011
comment
+1 для декартовых и полярных структур. Как правило, лучше всего создавать классы и структуры, которые непосредственно представляют данные, для которых они предназначены (в отличие от общей структуры Vec). Ваш Factory - тоже хороший пример, но ваш пример не показывает, кому принадлежит указатель 'a'. Если Factory 'f' владеет им, то он, вероятно, будет уничтожен, когда 'f' покинет область видимости, но если 'f' не владеет им, разработчику важно не забыть освободить эту память, иначе утечка памяти может происходить. - person David Peterson; 02.03.2013
comment
Конечно, объект можно удалить по ссылке! См. stackoverflow.com/a/752699/404734 Это, конечно, поднимает вопрос, разумно ли возвращать динамическую память по ссылке. , из-за проблемы потенциального присвоения возвращаемого значения копией (вызывающий, конечно, также может сделать что-то вроде int a = * returnsAPoninterToInt (), а затем столкнется с той же проблемой, если будет возвращена динамически покрытая памятью, как для ссылок , но в версии с указателем пользователь должен явно разыменовать вместо того, чтобы просто забыть явно ссылаться, чтобы ошибаться). - person Kaiserludi; 12.09.2013
comment
@Kaiserludi, хорошая мысль. Я не думал об этом, но это все еще злой способ делать что-то. Отредактировал свой ответ, чтобы отразить это. - person Sergei Tachenov; 14.09.2013
comment
А как насчет создания различных неполиморфных классов, которые являются неизменяемыми? Подходит ли тогда фабричный шаблон для использования в C ++? - person daaxix; 14.03.2014
comment
@daaxix, зачем вам фабрика для создания экземпляров неполиморфного класса? Я не понимаю, при чем тут неизменность. - person Sergei Tachenov; 15.03.2014
comment
@SergeyTachenov Предположим, нам нужны разные (но не полиморфные) экземпляры сложного неизменяемого класса. После создания экземпляра класса с желаемыми свойствами его больше нельзя будет изменить. Фабрика (или фабрика, такая как вспомогательная функция / класс), кажется, здесь хорошо подходит, чтобы упростить создание неизменяемого класса, когда детали класса сложны, и все они должны быть установлены при инициализации. Есть ли способ лучше? - person daaxix; 18.03.2014
comment
@daaxix, если это невозможно сделать с помощью конструктора, то фабрика или, скорее, шаблон построителя - правильный путь. Если это можно сделать с помощью конструктора, то лучше с помощью конструктора. И я не знаю, насколько сложно превратить конструктор в кошмар. - person Sergei Tachenov; 19.03.2014
comment
Мне не нравится решение декартовых / полярных структур. Вектор один и тот же, независимо от представления, и реализация таких вещей, как добавление или копирование векторов, потребует декартово-декартовых, декартовых-полярных, полярно-декартовых и полярно-полярных перегрузок, что было бы утомительно. - person Kevin; 03.01.2015
comment
@Kevin, я только предложил ввести разные конструкторы, чтобы избежать недопустимой проблемы с перегрузкой. После того, как вектор был построен, он остается прежним, так зачем вам нужны разные перегрузки для копирования или добавления? В то время даже не было известно, какое изображение использовалось при строительстве! Считайте Vec2(Cartesian(x, y)) своего рода синтаксическим сахаром для обозначения системы координат. Вам понадобятся разные перегрузки только при написании конструкторов для подкласса, но для этой цели это все же лучше, чем фабричный метод. - person Sergei Tachenov; 04.01.2015
comment
Ой, как плохо. Я неправильно понял ваше решение. Вы просто хотите использовать полярные и декартовы объекты при построении объектов Vec2. Я думал, вы предлагаете два отдельных класса - полярный и декартовский - которые, по сути, реализуют интерфейс Vec2 или что-то подобное. Теперь, когда я правильно это понимаю, мне гораздо больше нравится ваше решение - извините. - person Kevin; 04.01.2015
comment
Вместо создания новых структур вы можете просто выполнить `typedef float Angle 'и использовать его в допустимой перегрузке. Кажется странным в этом сценарии, поскольку вам нужно будет static_cast в вызове конструктора, но в других случаях может быть хорошо. (например, если исходный тип настолько сложен, что вы все равно используете локальную переменную. - person pkubik; 15.11.2015
comment
@pkubik, это стандартное поведение? Я думал, что typedef - это просто псевдонимы, не имеющие отношения к перегрузкам, и что большинство компиляторов в любом случае рассматривают тип как оригинал. Фактически, я только что попробовал это в MSVS 2013, и он выдает ошибку member function already defined or declared во втором конструкторе, даже если я не пытаюсь использовать конструкторы. - person Sergei Tachenov; 16.11.2015
comment
@SergeyTachenov, плохо, ты прав. Понятия не имею, как я мог так ошибиться. В любом случае вы можете эмулировать константные сильные определения типов, объявив класс с конструктором, принимающим оператор float и float-cast. Также обратите внимание, что указание обертки на единицу может использоваться с пользовательскими литералами C ++ 11. - person pkubik; 16.11.2015
comment
Красиво и естественно. После того, как вы определили Polar и Cartesian, я в первую очередь задаю вопрос: «Чем хорош сейчас класс Vec2?» - person Laurent LA RIZZA; 19.08.2018
comment
@Kevin: Я, напротив, выступаю за использование Polar и Cartesian повсюду вместо Vec2. По сути, для Vec2 не так много интерфейса, кроме математических операций (путем перегрузки операторов для ваших типов), преобразования друг в друга (путем определения операторов преобразования), норм (путем определения одной перегруженной бесплатной функции). Перегрузка является частью C ++ и действует как большое невидимое во время компиляции if-else для типов. Перегрузка операторов и неявные / явные преобразования также являются частью C ++. Это функции, добавленные в C ++. Мы не должны бороться с языком. - person Laurent LA RIZZA; 19.08.2018

Простой заводской пример:

// 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();
    }
};
person Martin York    schedule 25.02.2011
comment
@LokiAstari Потому что использование умных указателей - самый простой способ потерять контроль над памятью. Контроль над тем, какие языки C / C ++ считаются лучшими по сравнению с другими языками, и от которых они получают наибольшее преимущество. Не говоря уже о том, что интеллектуальные указатели производят накладные расходы на память, аналогичные другим управляемым языкам. Если вам нужно удобство автоматического управления памятью, начните программировать на Java или C #, но не добавляйте этот беспорядок в C / C ++. - person luke1985; 20.04.2014
comment
Очень интересная идея иметь контейнер с объектами на фабрике - person BЈовић; 07.05.2014
comment
@ lukasz1985 unique_ptr в этом примере не влияет на производительность. Управление ресурсами, включая память, является одним из главных преимуществ C ++ по сравнению с любым другим языком, потому что вы можете делать это без потери производительности и детерминированно, без потери контроля, но вы говорите с точностью до наоборот. Некоторым не нравится то, что C ++ делает неявно, например, управление памятью с помощью интеллектуальных указателей, но если вы хотите, чтобы все было обязательно явным, используйте C; компромисс - на порядок меньше проблем. Я считаю несправедливым, что вы отклоняете хорошую рекомендацию. - person TheCppZoo; 13.06.2015
comment
@EdMaster: Я не отвечал раньше, потому что он явно троллинг. Пожалуйста, не кормите тролля. - person Martin York; 14.06.2015
comment
@LokiAstari он может быть троллем, но то, что он говорит, может сбить с толку людей - person TheCppZoo; 14.06.2015
comment
Между прочим, с C ++ 11 в примере с сохранением ссылки также можно заменить boost::ptr_vector<Foo> на std::vector<std::unique_ptr<Foo>> плюс небольшие соответствующие адаптации. - person yau; 05.01.2016
comment
@yau: Да. Но: boost::ptr_vector<> немного эффективнее, поскольку понимает, что владеет указателем, а не делегирует работу подклассу. НО главным преимуществом boost::ptr_vector<> является то, что он предоставляет свои элементы по ссылке (а не по указателю), поэтому его действительно легко использовать с алгоритмами в стандартной библиотеке. - person Martin York; 05.01.2016
comment
В настоящее время у меня возникла проблема, когда я попытался быть хорошим кодировщиком и вернул unique_ptr ‹Base›, однако позже в коде нам нужно динамически преобразовать это значение в производный тип, по крайней мере, иногда. Это заканчивается тем, что динамическое приведение unique_ptr не должно быть выполнимой задачей. Итак .... головоломка. Он работал в VC100 с использованием листинга из stackoverflow, но после этого не работает. - person Christopher Pisz; 19.09.2017
comment
@ChristopherPisz: Это то, что вы ищете? Как выполнить dynamic_cast с unique_ptr - person Martin York; 20.09.2017
comment
@Loku Astari Да, я был на этой странице, но ответ Джарода небезопасен и просачивается, когда приведение не выполняется, а ответ Сехе даже не компилируется в vc140. Я преодолел проблему, используя код, который я оставил в комментарии к ответу Сехе с тех пор. - person Christopher Pisz; 20.09.2017

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

Вариант 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);

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

person Evan Teran    schedule 28.02.2011

Вы можете прочитать очень хорошее решение в: 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");
person mabg    schedule 15.11.2014

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

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

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

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

person mbrcknl    schedule 07.09.2015
comment
Я широко использовал это при упаковке ресурсов OpenGL. Удалены конструкторы копирования и назначение копирования, что заставляет использовать семантику перемещения. Затем я создал несколько статических фабричных методов для создания каждого типа ресурсов. Это было намного удобнее для чтения, чем диспетчер времени выполнения на основе перечисления OpenGL, который часто имеет множество избыточных параметров функции в зависимости от переданного перечисления. Это очень полезный шаблон, удивлен, что ответ не выше. - person Fibbs; 28.05.2018

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

person Jerry Coffin    schedule 25.02.2011
comment
Даже если он устарел (что я оспариваю), он все равно отлично работает. Я все еще использую Factory, основанный на MC ++ D, в новом проекте C ++ 14, и это очень эффективно! Более того, паттерны Factory и Singleton, вероятно, являются наименее устаревшими частями. Хотя части Loki, такие как Function, и манипуляции с типами могут быть заменены на std::function и <type_traits>, и хотя лямбды, потоки, ссылки rvalue имеют последствия, которые могут потребовать некоторой незначительной настройки, стандартной замены для одиночных фабрик в том виде, в каком он их описывает, не существует. - person metal; 03.02.2017

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

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

Фактически, этот класс является Builder, а не Factory.

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

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

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

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

Действительно. Все шаблоны проектирования имеют свои (зависящие от языка) ограничения и недостатки. Их рекомендуется использовать только тогда, когда они помогают решить вашу проблему, а не ради них самих.

Если вам нужна «идеальная» реализация фабрики, удачи.

person Péter Török    schedule 25.02.2011
comment
Спасибо за ответ! Но не могли бы вы объяснить, как использование интеллектуального указателя снимает ограничение динамического распределения? Я не совсем понял эту роль. - person Kos; 25.07.2011
comment
@Kos, с помощью умных указателей вы можете скрыть выделение / освобождение фактического объекта от ваших пользователей. Они видят только инкапсулирующий интеллектуальный указатель, который для внешнего мира ведет себя как статически выделенный объект. - person Péter Török; 26.07.2011
comment
@Kos, не в строгом смысле этого слова, AFAIR. Вы передаете объект для обертывания, который вы, вероятно, в какой-то момент выделяли динамически. Затем интеллектуальный указатель становится владельцем и обеспечивает его правильное уничтожение, когда он больше не нужен (время которого определяется по-разному для разных типов интеллектуальных указателей). - person Péter Török; 26.07.2011

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

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

Заводской узор

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

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

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

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

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

person Florian Richoux    schedule 14.02.2014