Cómo implementar el patrón de método de fábrica en C ++ correctamente

329

Hay una cosa en C ++ que me ha estado haciendo sentir incómoda durante bastante tiempo, porque honestamente no sé cómo hacerlo, aunque parezca simple:

¿Cómo implemento Factory Method en C ++ correctamente?

Objetivo: hacer posible que el cliente pueda crear instancias de algún objeto utilizando métodos de fábrica en lugar de los constructores del objeto, sin consecuencias inaceptables y un impacto en el rendimiento.

Por "patrón de método de fábrica", me refiero a ambos métodos de fábrica estáticos dentro de un objeto o métodos definidos en otra clase, o funciones globales. En general, "el concepto de redirigir la forma normal de creación de instancias de la clase X a cualquier otro lugar que no sea el constructor".

Permítanme leer algunas posibles respuestas en las que he pensado.


0) No hagas fábricas, haz constructores.

Esto suena bien (y de hecho a menudo es la mejor solución), pero no es un remedio general. En primer lugar, hay casos en que la construcción de objetos es una tarea lo suficientemente compleja como para justificar su extracción a otra clase. Pero incluso dejando de lado ese hecho, incluso para objetos simples que usan solo constructores a menudo no funcionan.

El ejemplo más simple que conozco es una clase de vectores 2D. Tan simple, pero complicado. Quiero poder construirlo tanto desde coordenadas cartesianas como polares. Obviamente, no puedo hacer:

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

Mi forma natural de pensar es entonces:

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

Lo que, en lugar de los constructores, me lleva al uso de métodos estáticos de fábrica ... lo que esencialmente significa que estoy implementando el patrón de fábrica, de alguna manera ("la clase se convierte en su propia fábrica"). Esto se ve bien (y sería adecuado para este caso en particular), pero falla en algunos casos, que voy a describir en el punto 2. Siga leyendo.

otro caso: tratar de sobrecargar por dos tipos de letra opacos de alguna API (como GUID de dominios no relacionados, o un GUID y un campo de bits), tipos semánticamente totalmente diferentes (por lo tanto, en teoría, sobrecargas válidas) pero que en realidad resultan ser lo mismo, como entradas sin signo o punteros nulos.


1) La forma de Java

Java lo tiene simple, ya que solo tenemos objetos asignados dinámicamente. Hacer una fábrica es tan trivial como:

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

En C ++, esto se traduce en:

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

¿Frio? A menudo, de hecho. Pero entonces, esto obliga al usuario a usar solo la asignación dinámica. La asignación estática es lo que hace que C ++ sea complejo, pero también es lo que a menudo lo hace poderoso. Además, creo que existen algunos objetivos (palabra clave: incrustado) que no permiten la asignación dinámica. Y eso no implica que a los usuarios de esas plataformas les guste escribir OOP limpio.

De todos modos, dejando de lado la filosofía: en el caso general, no quiero obligar a los usuarios de la fábrica a restringir la asignación dinámica.


2) Retorno por valor

OK, entonces sabemos que 1) es genial cuando queremos una asignación dinámica. ¿Por qué no agregaremos asignación estática además de eso?

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

¿Qué? ¿No podemos sobrecargar por el tipo de retorno? Oh, por supuesto que no podemos. Así que cambiemos los nombres de los métodos para reflejar eso. Y sí, he escrito el ejemplo de código no válido anterior solo para enfatizar cuánto me disgusta la necesidad de cambiar el nombre del método, por ejemplo, porque ahora no podemos implementar un diseño de fábrica independiente del idioma, ya que tenemos que cambiar los nombres, y Todos los usuarios de este código deberán recordar esa diferencia de la implementación de la especificación.

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

OK ... ahí lo tenemos. Es feo, ya que necesitamos cambiar el nombre del método. Es imperfecto, ya que necesitamos escribir el mismo código dos veces. Pero una vez hecho, funciona. ¿Correcto?

Bueno, por lo general. Pero a veces no. Al crear Foo, en realidad dependemos del compilador para hacer la optimización del valor de retorno para nosotros, porque el estándar C ++ es lo suficientemente benevolente para que los proveedores del compilador no especifiquen cuándo se creará el objeto en el lugar y cuándo se copiará al devolver un objeto temporal por valor en C ++. Entonces, si Foo es costoso de copiar, este enfoque es arriesgado.

¿Y si Foo no es copiable en absoluto? Bueno doh. ( Tenga en cuenta que en C ++ 17 con elisión de copia garantizada, no ser copiable ya no es un problema para el código anterior )

Conclusión: Hacer una fábrica devolviendo un objeto es de hecho una solución para algunos casos (como el vector 2-D mencionado anteriormente), pero aún no es un reemplazo general para los constructores.


3) construcción de dos fases

Otra cosa que probablemente se le ocurrirá a alguien es separar el tema de la asignación de objetos y su inicialización. Esto generalmente da como resultado un código como este:

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());
    // ...
}

Uno puede pensar que funciona como un encanto. El único precio que pagamos en nuestro código ...

Como he escrito todo esto y lo he dejado como el último, tampoco me gusta. :) ¿Por qué?

En primer lugar ... me disgusta sinceramente el concepto de construcción en dos fases y me siento culpable cuando lo uso. Si diseño mis objetos con la afirmación de que "si existe, está en estado válido", siento que mi código es más seguro y menos propenso a errores. Me gusta de esa forma.

Tener que abandonar esa convención Y cambiar el diseño de mi objeto solo con el propósito de fabricarlo es ... bueno, difícil de manejar.

Sé que lo anterior no convencerá a muchas personas, así que permítanme dar algunos argumentos más sólidos. Usando la construcción de dos fases, no puede:

  • inicializar consto referenciar las variables miembro,
  • pasar argumentos a constructores de clase base y constructores de objetos miembros.

Y probablemente podría haber algunos inconvenientes más en los que no puedo pensar en este momento, y ni siquiera me siento particularmente obligado ya que los puntos anteriores ya me convencen.

Entonces: ni siquiera cerca de una buena solución general para implementar una fábrica.


Conclusiones:

Queremos tener una forma de instanciación de objetos que:

  • permitir una instanciación uniforme independientemente de la asignación,
  • dar nombres diferentes y significativos a los métodos de construcción (por lo tanto, no depender de la sobrecarga de argumentos),
  • no introducir un impacto significativo en el rendimiento y, preferiblemente, un impacto significativo en el aumento de código, especialmente en el lado del cliente,
  • ser general, como en: posible ser introducido para cualquier clase.

Creo que he demostrado que las formas que he mencionado no cumplen esos requisitos.

¿Alguna pista? Por favor, proporciónenme una solución. No quiero pensar que este lenguaje no me permita implementar adecuadamente un concepto tan trivial.

Kos
fuente
77
@Zac, aunque el título es muy similar, las preguntas reales son en mi humilde opinión diferentes.
Péter Török
2
Buen duplicado pero el texto de esta pregunta es valioso en sí mismo.
dmckee --- ex-gatito moderador
77
Dos años después de preguntar esto, tengo algunos puntos que agregar: 1) Esta pregunta es relevante para varios patrones de diseño (fábrica [abstracta], constructor, lo que sea, no me gusta profundizar en su taxonomía). 2) El problema real que se discute aquí es "¿cómo desacoplar limpiamente la asignación de almacenamiento de objetos de la construcción de objetos?".
Kos
1
@ Dennis: solo si no lo haces delete. Este tipo de métodos están perfectamente bien, siempre y cuando esté "documentado" (el código fuente es documentación ;-)) que la persona que llama toma posesión del puntero (léase: es responsable de eliminarlo cuando sea apropiado).
Boris Dalstein
1
@Boris @Dennis también podría hacerlo muy explícito devolviendo un en unique_ptr<T>lugar de T*.
Kos

Respuestas:

107

En primer lugar, hay casos en que la construcción de objetos es una tarea lo suficientemente compleja como para justificar su extracción a otra clase.

Creo que este punto es incorrecto. La complejidad realmente no importa. La relevancia es lo que hace. Si un objeto se puede construir en un solo paso (no como en el patrón de construcción), el constructor es el lugar correcto para hacerlo. Si realmente necesita otra clase para realizar el trabajo, entonces debería ser una clase auxiliar que se utiliza desde el constructor de todos modos.

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

Hay una solución fácil para esto:

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

La única desventaja es que se ve un poco detallado:

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

Pero lo bueno es que puede ver de inmediato qué tipo de coordenadas está utilizando y, al mismo tiempo, no tiene que preocuparse por copiar. Si desea copiar, y es costoso (como lo demuestra el perfil, por supuesto), es posible que desee usar algo como las clases compartidas de Qt para evitar la sobrecarga de la copia.

En cuanto al tipo de asignación, la razón principal para usar el patrón de fábrica suele ser el polimorfismo. Los constructores no pueden ser virtuales, e incluso si pudieran, no tendría mucho sentido. Al usar la asignación estática o de pila, no puede crear objetos de forma polimórfica porque el compilador necesita saber el tamaño exacto. Por lo tanto, solo funciona con punteros y referencias. Y devolver una referencia de una fábrica no funciona también, porque si bien es un objeto técnicamente, se puede eliminar por referencia, podría ser bastante confuso y propenso a errores, consulte ¿Es mal la práctica de devolver una variable de referencia de C ++?por ejemplo. Entonces, los punteros son lo único que queda, y eso también incluye punteros inteligentes. En otras palabras, las fábricas son más útiles cuando se usan con asignación dinámica, por lo que puede hacer cosas como esta:

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

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

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

En otros casos, las fábricas solo ayudan a resolver problemas menores como aquellos con sobrecargas que usted mencionó. Sería bueno si fuera posible usarlos de manera uniforme, pero no duele mucho que probablemente sea imposible.

Sergei Tachenov
fuente
21
+1 para estructuras cartesianas y polares. En general, es mejor crear clases y estructuras que representen directamente los datos a los que están destinados (en oposición a una estructura Vec general). Your Factory también es un buen ejemplo, pero su ejemplo no ilustra quién posee el puntero 'a'. Si la fábrica 'f' lo posee, entonces probablemente se destruirá cuando 'f' deje el alcance, pero si 'f' no lo posee, es importante que el desarrollador recuerde liberar esa memoria o, de lo contrario, una pérdida de memoria puede ocurrir.
David Peterson
1
¡Por supuesto, un objeto se puede eliminar por referencia! Consulte stackoverflow.com/a/752699/404734 Eso, por supuesto, plantea la cuestión de si es aconsejable devolver la memoria dinámica por referencia, debido al problema de la posible asignación del valor de retorno por copia (la persona que llama, por supuesto, también podría hacer algo como int a = * devuelveAPoninterToInt () y luego se enfrentaría al mismo problema, si se devuelve dinámicamente la memoria revestida, como para las referencias, pero en la versión de puntero, el usuario tiene que desreferenciar explícitamente en lugar de simplemente olvidar hacer referencia explícitamente, estar equivocado) .
Kaiserludi
1
@Kaiserludi, buen punto. No pensé en eso, pero sigue siendo una forma "malvada" de hacer las cosas. Edité mi respuesta para reflejar eso.
Sergei Tachenov
¿Qué pasa con la creación de diferentes clases no polimórficas que son inmutables? ¿Es apropiado usar un patrón de fábrica en C ++?
daaxix
@daaxix, ¿por qué necesitarías una fábrica para crear instancias de una clase no polimórfica? No veo qué tiene que ver la inmutabilidad con nada de esto.
Sergei Tachenov
49

Ejemplo simple de fábrica:

// 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();
    }
};
Martin York
fuente
2
@LokiAstari Porque el uso de punteros inteligentes es la forma más sencilla de perder el control sobre la memoria. El control de qué lang C / C ++ se sabe que es supremo en comparación con otros lenguajes, y de los cuales obtienen la mayor ventaja. Sin mencionar el hecho de que los punteros inteligentes producen sobrecarga de memoria similar a otros lenguajes administrados. Si desea la conveniencia de la administración automática de memoria, comience a programar en Java o C # pero no ponga ese desorden en C / C ++.
luke1985
45
@ lukasz1985 el unique_ptren ese ejemplo no tiene sobrecarga de rendimiento. Administrar recursos, incluida la memoria, es una de las ventajas supremas de C ++ sobre cualquier otro lenguaje porque puede hacerlo sin penalización de rendimiento y de manera determinista, sin perder el control, pero usted dice exactamente lo contrario. A algunas personas no les gustan las cosas que C ++ hace implícitamente, como la administración de memoria a través de punteros inteligentes, pero si lo que quieres es que todo sea obligatoriamente explícito, usa C; la compensación es de orden de magnitud menos problemas. Creo que es injusto que rechaces una buena recomendación.
TheCppZoo
1
@EdMaster: no respondí anteriormente porque obviamente estaba trolleando. Por favor no alimentes al troll.
Martin York
17
@LokiAstari podría ser un troll, pero lo que dice podría confundir a la gente
TheCppZoo
1
@yau: sí. Pero: boost::ptr_vector<>es un poco más eficiente ya que entiende que posee el puntero en lugar de delegar el trabajo a una subclase. PERO la principal ventaja boost::ptr_vector<>es que expone a sus miembros por referencia (no puntero), por lo que es realmente fácil de usar con algoritmos en la biblioteca estándar.
Martin York
41

¿Has pensado en no usar una fábrica en absoluto y, en cambio, hacer un buen uso del sistema de tipos? Puedo pensar en dos enfoques diferentes que hacen este tipo de cosas:

Opción 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) { /* ... */ }
};

Lo que te permite escribir cosas como:

Vec2 v(linear(1.0, 2.0));

Opcion 2:

puede usar "etiquetas" como lo hace el STL con iteradores y demás. Por ejemplo:

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 &) { /* ... */ }
};

Este segundo enfoque le permite escribir código que se ve así:

Vec2 v(1.0, 2.0, linear_coord);

que también es agradable y expresivo al tiempo que te permite tener prototipos únicos para cada constructor.

Evan Teran
fuente
29

Puedes leer una muy buena solución en: http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

La mejor solución está en los "comentarios y discusiones", consulte "No es necesario utilizar métodos de creación estáticos".

A partir de esta idea, he hecho una fábrica. Tenga en cuenta que estoy usando Qt, pero puede cambiar QMap y QString para equivalentes estándar.

#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

Uso de la muestra:

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
fuente
17

Principalmente estoy de acuerdo con la respuesta aceptada, pero hay una opción de C ++ 11 que no se ha cubierto en las respuestas existentes:

  • Devuelva los resultados del método de fábrica por valor , y
  • Proporcionar un constructor de movimiento barato .

Ejemplo:

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

Entonces puedes construir objetos en la pila:

sandwich mine{sandwich::ham()};

Como subobjetos de otras cosas:

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

O asignado dinámicamente:

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

¿Cuándo podría usar esto?

Si, en un constructor público, no es posible dar inicializadores significativos para todos los miembros de la clase sin algún cálculo preliminar, entonces podría convertir ese constructor a un método estático. El método estático realiza los cálculos preliminares, luego devuelve un resultado de valor a través de un constructor privado que solo realiza una inicialización a nivel de miembro.

Digo ' podría ' porque depende de qué enfoque proporcione el código más claro sin ser innecesariamente ineficiente.

mbrcknl
fuente
1
Usé esto extensamente cuando envolví los recursos de OpenGL. Los constructores de copia eliminados y la asignación de copia obligan al uso de la semántica de movimiento. Luego creé un montón de métodos de fábrica estáticos para crear cada tipo de recurso. Esto era mucho más legible que el despacho de tiempo de ejecución basado en enumeración de OpenGL que a menudo tiene un montón de parámetros de función redundantes dependiendo de la enumeración pasada. Es un patrón muy útil, me sorprende que esta respuesta no esté más arriba.
Fibbles
11

Loki tiene un método de fábrica y una fábrica abstracta . Ambos están documentados (ampliamente) en Modern C ++ Design , por Andei Alexandrescu. El método de fábrica probablemente esté más cerca de lo que parece ser, aunque sigue siendo un poco diferente (al menos si la memoria sirve, requiere que registre un tipo antes de que la fábrica pueda crear objetos de ese tipo).

Jerry Coffin
fuente
1
Incluso si está desactualizado (lo cual disputo), sigue siendo perfectamente útil. Todavía uso una fábrica basada en MC ++ D's en un nuevo proyecto C ++ 14 con gran efecto. Además, los patrones Factory y Singleton son probablemente las partes menos desactualizadas. Si bien las piezas de Loki Functiony las manipulaciones de tipo pueden reemplazarse con std::functiony, <type_traits>mientras que las referencias lambda, threading y rvalue tienen implicaciones que pueden requerir algunos ajustes menores, no hay un reemplazo estándar para los singletons de fábricas tal como los describe.
metal
5

No trato de responder todas mis preguntas, ya que creo que es demasiado amplio. Solo un par de notas:

Hay casos en que la construcción de objetos es una tarea lo suficientemente compleja como para justificar su extracción a otra clase.

Esa clase es, de hecho, un constructor , en lugar de una fábrica.

En el caso general, no quiero obligar a los usuarios de la fábrica a restringir la asignación dinámica.

Entonces podría hacer que su fábrica lo encapsule en un puntero inteligente. Creo que de esta manera puedes tener tu pastel y comértelo también.

Esto también elimina los problemas relacionados con el retorno por valor.

Conclusión: Hacer una fábrica devolviendo un objeto es de hecho una solución para algunos casos (como el vector 2-D mencionado anteriormente), pero aún no es un reemplazo general para los constructores.

En efecto. Todos los patrones de diseño tienen sus limitaciones y desventajas (específicas del idioma). Se recomienda usarlos solo cuando lo ayuden a resolver su problema, no por su propio bien.

Si buscas la implementación de fábrica "perfecta", buena suerte.

Péter Török
fuente
¡Gracias por la respuesta! Pero, ¿podría explicar cómo usar un puntero inteligente liberaría la restricción de la asignación dinámica? No entendí bien esta parte.
Kos
@Kos, con punteros inteligentes puede ocultar la asignación / desasignación del objeto real de sus usuarios. Solo ven el puntero inteligente encapsulado, que para el mundo exterior se comporta como un objeto asignado estáticamente.
Péter Török
@Kos, no en sentido estricto, AFAIR. Usted pasa el objeto a envolver, que probablemente haya asignado dinámicamente en algún momento. Luego, el puntero inteligente se apropia de él y se asegura de que se destruya correctamente cuando ya no sea necesario (el tiempo se decide de manera diferente para diferentes tipos de punteros inteligentes).
Péter Török
3

Esta es mi solución de estilo c ++ 11. El parámetro 'base' es para la clase base de todas las subclases. los creadores, son objetos std :: function para crear instancias de subclase, podrían ser un enlace a su subclase 'función miembro estática' create (algunos argumentos) '. Esto tal vez no sea perfecto, pero funciona para mí. Y es una solución algo "general".

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_;
};

Un ejemplo de uso.

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();
  }
}
TROZO DE CUERO
fuente
Se ve bien para mi ¿Cómo implementaría (tal vez algo de magia macro) el registro estático? Solo imagine que la clase base es una clase de servicio para objetos. Las clases derivadas proporcionan un tipo especial de servicio a esos objetos. Y desea agregar progresivamente diferentes tipos de servicios agregando una clase derivada de la base para cada uno de esos tipos de servicios.
St0fF
2

Patrón de fábrica

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

Y si su compilador no admite la Optimización del valor de retorno, desactívela, probablemente no contenga mucha optimización en absoluto ...

Matthieu M.
fuente
¿Puede esto realmente considerarse una implementación del patrón de fábrica?
Dennis
1
@ Dennis: Como un caso degenerado, creo que sí. El problema Factoryes que es bastante genérico y cubre mucho terreno; una fábrica puede agregar argumentos (dependiendo del entorno / configuración) o proporcionar algo de almacenamiento en caché (relacionado con Flyweight / Pools), por ejemplo, pero estos casos solo tienen sentido en algunas situaciones.
Matthieu M.
Si tan solo cambiar el compilador fuera tan fácil como lo haces sonar :)
rozina
@rozina: :) Funciona bien en Linux (gcc / clang son notablemente compatibles); Admito que Windows todavía está relativamente cerrado, aunque debería mejorar en la plataforma de 64 bits (menos patentes en el camino, si no recuerdo mal).
Matthieu M.
Y luego tienes todo el mundo incrustado con algunos compiladores secundarios ... :) Estoy trabajando con uno como ese que no tiene optimización de valor de retorno. Aunque desearía que así fuera. Desafortunadamente, cambiarlo no es una opción en este momento. Con suerte en el futuro se actualizará o haremos un cambio para algo más :)
rozina 05 de