Como implementar o padrão de método de fábrica em C ++ corretamente

329

Existe uma coisa no C ++ que me deixa desconfortável há muito tempo, porque sinceramente não sei como fazê-lo, mesmo que pareça simples:

Como implementar corretamente o método de fábrica em C ++?

Objetivo: possibilitar ao cliente instanciar algum objeto usando métodos de fábrica em vez dos construtores do objeto, sem consequências inaceitáveis ​​e um impacto no desempenho.

Por "Padrão de método de fábrica", refiro-me aos métodos de fábrica estáticos dentro de um objeto ou métodos definidos em outra classe, ou funções globais. Geralmente "o conceito de redirecionar a maneira normal de instanciação da classe X para qualquer outro lugar que não o construtor".

Deixe-me examinar algumas respostas possíveis em que pensei.


0) Não faça fábricas, faça construtores.

Parece bom (e de fato geralmente a melhor solução), mas não é um remédio geral. Primeiro, há casos em que a construção de objetos é uma tarefa complexa o suficiente para justificar sua extração para outra classe. Mas mesmo colocando esse fato de lado, mesmo para objetos simples, usando apenas construtores, muitas vezes não servem.

O exemplo mais simples que conheço é uma classe vetorial 2-D. Tão simples, mas complicado. Quero ser capaz de construí-lo a partir de coordenadas cartesianas e polares. Obviamente, não posso fazer:

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

Minha maneira natural de pensar é então:

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

O que, em vez de construtores, me leva ao uso de métodos estáticos de fábrica ... o que essencialmente significa que estou implementando o padrão de fábrica de alguma forma ("a classe se torna sua própria fábrica"). Isso parece bom (e seria adequado para esse caso em particular), mas falha em alguns casos, que vou descrever no ponto 2. Continue lendo.

outro caso: tentar sobrecarregar por dois typedefs opacos de alguma API (como GUIDs de domínios não relacionados, ou um GUID e um campo de bits), tipos semanticamente totalmente diferentes (sobrecargas válidas, na teoria - válidas), mas que na verdade acabam sendo as mesma coisa - como entradas não assinadas ou ponteiros nulos.


1) O Caminho Java

Java é simples, pois temos apenas objetos alocados dinamicamente. Fazer uma fábrica é tão trivial quanto:

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

No C ++, isso se traduz em:

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

Legal? Muitas vezes, de fato. Mas então - isso força o usuário a usar apenas alocação dinâmica. A alocação estática é o que torna o C ++ complexo, mas também é o que frequentemente o torna poderoso. Além disso, acredito que existem alguns destinos (palavra-chave: incorporado) que não permitem alocação dinâmica. E isso não implica que os usuários dessas plataformas gostem de escrever OOP limpo.

Enfim, deixe a filosofia de lado: no caso geral, não quero forçar os usuários da fábrica a serem restringidos à alocação dinâmica.


2) Retorno por valor

OK, então sabemos que 1) é legal quando queremos alocação dinâmica. Por que não adicionamos alocação estática além disso?

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

O que? Não podemos sobrecarregar pelo tipo de retorno? Ah, claro que não podemos. Então, vamos mudar os nomes dos métodos para refletir isso. E sim, escrevi o exemplo de código inválido acima apenas para enfatizar o quanto não gosto da necessidade de alterar o nome do método, por exemplo, porque não podemos implementar um projeto de fábrica independente de idioma corretamente agora, pois precisamos alterar os nomes - e todo usuário desse código precisará se lembrar dessa diferença de implementação da especificação.

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

OK ... aí está. É feio, pois precisamos alterar o nome do método. É imperfeito, pois precisamos escrever o mesmo código duas vezes. Mas uma vez feito, ele funciona. Certo?

Bem, geralmente. Mas às vezes isso não acontece. Ao criar o Foo, na verdade, dependemos do compilador para fazer a otimização do valor de retorno para nós, porque o padrão C ++ é benevolente o suficiente para que os fornecedores do compilador não especifiquem quando o objeto será criado no local e quando será copiado ao retornar um objeto temporário por valor em C ++. Portanto, se é caro copiar o Foo, essa abordagem é arriscada.

E se Foo não for copiável? Bem, doh. ( Observe que no C ++ 17 com elision de cópia garantida, não ser copiável não é mais problema para o código acima )

Conclusão: Fazer uma fábrica retornando um objeto é realmente uma solução para alguns casos (como o vetor 2-D mencionado anteriormente), mas ainda não é um substituto geral para os construtores.


3) Construção bifásica

Outra coisa que alguém provavelmente sugeriria é separar a questão da alocação de objetos e sua inicialização. Isso geralmente resulta em 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());
    // ...
}

Pode-se pensar que funciona como um encanto. O único preço que pagamos em nosso código ...

Desde que eu escrevi tudo isso e deixei isso como o último, também devo não gostar. :) Por quê?

Primeiro de tudo ... Eu sinceramente não gosto do conceito de construção em duas fases e me sinto culpado quando o uso. Se eu projetar meus objetos com a asserção de que "se existe, está em estado válido", sinto que meu código é mais seguro e menos propenso a erros. Eu gosto assim.

Ter que abandonar essa convenção E alterar o design do meu objeto apenas com o objetivo de torná-lo fábrica é ... bem, pesado.

Sei que o exposto acima não convencerá muitas pessoas, então, deixe-me dar alguns argumentos mais sólidos. Usando construção em duas fases, você não pode:

  • inicializar constou referenciar variáveis ​​de membro,
  • passar argumentos para construtores de classe base e construtores de objetos membros.

E provavelmente poderia haver mais algumas desvantagens nas quais não consigo pensar agora, e nem me sinto particularmente obrigado, pois os pontos acima mencionados já me convencem.

Portanto: nem mesmo perto de uma boa solução geral para implementar uma fábrica.


Conclusões:

Queremos ter uma maneira de instanciação de objetos que:

  • permitir instanciação uniforme, independentemente da alocação,
  • atribuir nomes diferentes e significativos aos métodos de construção (sem depender da sobrecarga por argumentos),
  • não introduza um impacto significativo no desempenho e, de preferência, um impacto significativo no código, especialmente no lado do cliente,
  • seja geral, como em: possível de ser introduzido para qualquer classe.

Acredito ter provado que as maneiras mencionadas não atendem a esses requisitos.

Alguma dica? Por favor, me forneça uma solução, não quero pensar que essa linguagem não me permita implementar adequadamente um conceito tão trivial.

Kos
fonte
7
@ Zac, embora o título seja muito parecido, as perguntas reais são diferentes.
Péter Török
2
Bom duplicado, mas o texto desta pergunta é valioso por si só.
dmckee --- ex-moderador gatinho
7
Dois anos depois de fazer isso, tenho alguns pontos a acrescentar: 1) Esta pergunta é relevante para vários padrões de design (fábrica [abstrata], construtor, o que você quiser, não gosto de investigar sua taxonomia). 2) O problema real discutido aqui é "como separar de maneira limpa a alocação de armazenamento de objetos da construção de objetos?".
Kos
1
@ Dennis: apenas se você não fizer deleteisso. Esse tipo de método é perfeitamente adequado, desde que seja "documentado" (o código-fonte é a documentação ;-)) que o chamador se apropria do ponteiro (leia-se: é responsável por excluí-lo quando apropriado).
Boris Dalstein
1
@ Boris @ Dennis, você também pode deixar bem explícito retornando um em unique_ptr<T>vez de T*.
Kos

Respostas:

107

Primeiro, há casos em que a construção de objetos é uma tarefa complexa o suficiente para justificar sua extração para outra classe.

Eu acredito que este ponto está incorreto. A complexidade realmente não importa. A relevância é o que faz. Se um objeto puder ser construído em uma etapa (não como no padrão do construtor), o construtor é o lugar certo para fazê-lo. Se você realmente precisar de outra classe para executar o trabalho, deve ser uma classe auxiliar usada pelo construtor de qualquer maneira.

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

Existe uma solução fácil para isso:

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

A única desvantagem é que parece um pouco detalhada:

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

Mas o bom é que você pode ver imediatamente o tipo de coordenada que está usando e, ao mesmo tempo, não precisa se preocupar em copiar. Se você deseja copiar, e é caro (como comprovado pela criação de perfil, é claro), convém usar algo como as classes compartilhadas do Qt para evitar a sobrecarga de cópia.

Quanto ao tipo de alocação, o principal motivo para usar o padrão de fábrica é geralmente o polimorfismo. Os construtores não podem ser virtuais e, mesmo que pudessem, não faria muito sentido. Ao usar a alocação estática ou de pilha, não é possível criar objetos de forma polimórfica, pois o compilador precisa saber o tamanho exato. Portanto, ele funciona apenas com ponteiros e referências. E retornar uma referência de uma fábrica também não funciona, porque, embora um objeto tecnicamente possa ser excluído por referência, ele pode ser bastante confuso e propenso a erros, consulte A prática de retornar uma variável de referência C ++ é ruim?por exemplo. Portanto, ponteiros são a única coisa que resta, e isso inclui ponteiros inteligentes. Em outras palavras, as fábricas são mais úteis quando usadas com alocação dinâmica, para que você possa fazer coisas como estas:

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

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

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

Em outros casos, as fábricas apenas ajudam a resolver problemas menores, como aqueles com sobrecargas que você mencionou. Seria bom se fosse possível usá-los de maneira uniforme, mas não dói muito o que provavelmente é impossível.

Sergei Tachenov
fonte
21
+1 para estruturas cartesianas e polares. Geralmente, é melhor criar classes e estruturas que representem diretamente os dados a que se destinam (em oposição a uma estrutura geral do Vec). Sua fábrica também é um bom exemplo, mas seu exemplo não ilustra quem é o dono do ponteiro 'a'. Se o Factory 'f' for o proprietário, provavelmente será destruído quando 'f' sair do escopo, mas se 'f' não for o proprietário, é importante que o desenvolvedor lembre-se de liberar a memória ou então um vazamento de memória pode ocorrer. ocorrer.
David Peterson
1
Claro que um objeto pode ser excluído por referência! Veja stackoverflow.com/a/752699/404734 Isso, obviamente, levanta a questão de se é sensato retornar memória dinâmica por referência, devido ao problema de atribuir potencialmente o valor de retorno por cópia (o chamador também poderia, obviamente, fazer algo como int a = * retorna APoninterToInt () e enfrentaria o mesmo problema, se a memória revestida dinamicamente for retornada, como para referências, mas na versão do ponteiro, o usuário precisará desreferenciar explicitamente, em vez de simplesmente se referir explicitamente, para estar errado) .
Kaiserludi
1
@Kaiserludi, bom ponto. Eu não pensei nisso, mas ainda é uma maneira "má" de fazer as coisas. Editou minha resposta para refletir isso.
Sergei Tachenov 14/09
Que tal criar diferentes classes não polimórficas imutáveis? Um padrão de fábrica é apropriado para uso em C ++?
daaxix
@daaxix, por que você precisaria de uma fábrica para criar instâncias de uma classe não polimórfica? Não vejo o que imutabilidade tem a ver com isso.
Sergei Tachenov 15/03
49

Exemplo simples 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
fonte
2
@LokiAstari Porque o uso de ponteiros inteligentes é a maneira mais simples de perder o controle sobre a memória. O controle de quais idiomas C / C ++ são conhecidos por serem supremos em comparação com outros idiomas e dos quais eles obtêm a maior vantagem. Sem mencionar o fato de que ponteiros inteligentes produzem sobrecarga de memória semelhante a outros idiomas gerenciados. Se você deseja a conveniência do gerenciamento automático de memória, inicie a programação em Java ou C #, mas não coloque essa bagunça no C / C ++.
precisa saber é o seguinte
45
@ lukasz1985 unique_ptrnesse exemplo não tem sobrecarga de desempenho. O gerenciamento de recursos, incluindo memória, é uma das vantagens supremas do C ++ sobre qualquer outra linguagem, porque você pode fazê-lo sem penalidade de desempenho e deterministicamente, sem perder o controle, mas diz exatamente o contrário. Algumas pessoas não gostam do que o C ++ implica implicitamente, como o gerenciamento de memória por meio de ponteiros inteligentes, mas se o que você deseja é que tudo seja obrigatoriamente explícito, use C; o tradeoff é ordens de magnitude menos problemas. Acho injusto você rejeitar uma boa recomendação.
TheCppZoo
1
@ EdMaster: Eu não respondi anteriormente porque ele estava obviamente trollando. Por favor, não alimente o troll.
Martin York
17
@LokiAstari ele poderia ser um troll, mas o que ele diz pode confundir as pessoas
TheCppZoo
1
@yau: Sim. Mas: boost::ptr_vector<>é um pouco mais eficiente, pois entende que possui o ponteiro, em vez de delegar o trabalho a uma subclasse. Mas a principal vantagem boost::ptr_vector<>é que ela expõe seus membros por referência (não por ponteiro), portanto, é realmente fácil de usar com algoritmos na biblioteca padrão.
Martin York
41

Você já pensou em não usar uma fábrica e fazer bom uso do sistema de tipos? Eu posso pensar em duas abordagens diferentes que fazem esse tipo de coisa:

Opção 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) { /* ... */ }
};

O que permite escrever coisas como:

Vec2 v(linear(1.0, 2.0));

Opção 2:

você pode usar "tags" como o STL faz com os iteradores e outros. Por exemplo:

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

Essa segunda abordagem permite escrever um código parecido com este:

Vec2 v(1.0, 2.0, linear_coord);

o que também é bom e expressivo, permitindo que você tenha protótipos exclusivos para cada construtor.

Evan Teran
fonte
29

Você pode ler uma solução muito boa em: http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

A melhor solução é nos "comentários e discussões", consulte "Não há necessidade de métodos estáticos de criação".

A partir dessa idéia, eu fiz uma fábrica. Observe que estou usando o Qt, mas você pode alterar o QMap e o QString por equivalentes 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

Uso da amostra:

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

Concordo principalmente com a resposta aceita, mas há uma opção C ++ 11 que não foi abordada nas respostas existentes:

  • Retorne os resultados do método de fábrica por valor e
  • Forneça um construtor de movimento barato .

Exemplo:

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

Então você pode construir objetos na pilha:

sandwich mine{sandwich::ham()};

Como subobjetos de outras coisas:

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

Ou alocado dinamicamente:

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

Quando devo usar isso?

Se, em um construtor público, não for possível fornecer inicializadores significativos para todos os membros da classe sem algum cálculo preliminar, eu poderia converter esse construtor em um método estático. O método estático executa os cálculos preliminares e, em seguida, retorna um resultado de valor por meio de um construtor privado, que apenas faz uma inicialização por membro.

Eu digo ' poder ' porque depende de qual abordagem fornece o código mais claro sem ser desnecessariamente ineficiente.

mbrcknl
fonte
1
Eu usei isso extensivamente ao agrupar recursos OpenGL. Construtores de cópia excluídos e atribuição de cópia forçando o uso de semântica de movimentação. Criei vários métodos de fábrica estáticos para criar cada tipo de recurso. Isso era muito mais legível do que o despacho de tempo de execução baseado em enum do OpenGL, que geralmente possui vários parâmetros de função redundantes, dependendo do enum passado. É um padrão muito útil, surpreso que esta resposta não esteja acima.
Fibbles
11

Loki tem um método de fábrica e uma fábrica abstrata . Ambos estão documentados (extensivamente) em Modern C ++ Design , por Andei Alexandrescu. O método de fábrica provavelmente está mais próximo do que você procura, embora ainda seja um pouco diferente (pelo menos se a memória servir, é necessário registrar um tipo antes que a fábrica possa criar objetos desse tipo).

Jerry Coffin
fonte
1
Mesmo que esteja desatualizado (o que eu contesto), ainda é perfeitamente reparável. Eu ainda uso uma fábrica baseada em MC ++ D em um novo projeto C ++ 14 com grande efeito! Além disso, os padrões Factory e Singleton são provavelmente as partes menos antigas. Enquanto pedaços de Loki gostam Functione as manipulações de tipo podem ser substituídas por std::functione <type_traits>enquanto lambdas, threading, rvalue refs têm implicações que podem exigir alguns ajustes menores, não há substituição padrão para singletons de fábricas como ele os descreve.
de metal
5

Não tento responder a todas as minhas perguntas, pois acredito que é muito amplo. Apenas algumas notas:

há casos em que a construção de objetos é uma tarefa complexa o suficiente para justificar sua extração para outra classe.

Essa classe é de fato um construtor , e não uma fábrica.

No caso geral, não quero forçar os usuários da fábrica a serem restringidos à alocação dinâmica.

Então você poderá encapsular sua fábrica em um ponteiro inteligente. Eu acredito que assim você pode comer o seu bolo e comê-lo também.

Isso também elimina os problemas relacionados ao retorno por valor.

Conclusão: Fazer uma fábrica retornando um objeto é realmente uma solução para alguns casos (como o vetor 2-D mencionado anteriormente), mas ainda não é um substituto geral para os construtores.

De fato. Todos os padrões de design têm suas limitações e desvantagens (específicas do idioma). Recomenda-se usá-los apenas quando eles ajudarem a resolver seu problema, não por eles mesmos.

Se você está buscando a implementação "perfeita" da fábrica, boa sorte.

Péter Török
fonte
Obrigado pela resposta! Mas você poderia explicar como o uso de um ponteiro inteligente liberaria a restrição da alocação dinâmica? Não entendi bem essa parte.
Kos
@Kos, com ponteiros inteligentes, você pode ocultar a alocação / desalocação do objeto real dos seus usuários. Eles vêem apenas o ponteiro inteligente encapsulado, que para o mundo exterior se comporta como um objeto alocado estaticamente.
Péter Török
@ Kos, não no sentido estrito, AFAIR. Você passa o objeto a ser quebrado, o qual provavelmente alocou dinamicamente em algum momento. Em seguida, o ponteiro inteligente se apropria dele e garante que ele seja destruído adequadamente quando não for mais necessário (cujo tempo é decidido de maneira diferente para diferentes tipos de ponteiros inteligentes).
Péter Török
3

Esta é a minha solução de estilo c ++ 11. O parâmetro 'base' é para a classe base de todas as subclasses. criadores, são objetos std :: function para criar instâncias de subclasses, podem ser uma ligação à sua subclasse 'static member function' create (some args) '. Isso talvez não seja perfeito, mas funciona para mim. E é meio que uma solução 'geral'.

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

Um exemplo 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();
  }
}
DAG
fonte
Parece bom para mim. Como você implementaria (talvez alguma macro mágica) o registro estático? Imagine a classe base sendo alguma classe de serviço para objetos. As classes derivadas fornecem um tipo especial de manutenção para esses objetos. E você deseja adicionar progressivamente diferentes tipos de serviços adicionando uma classe derivada da base para cada um desses tipos de serviços.
St0fF 7/09/19
2

Padrão de fábrica

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

E se o compilador não oferecer suporte à Otimização do Valor de Retorno, evite-o, provavelmente não conterá muita otimização ...

Matthieu M.
fonte
Isso pode realmente ser considerado uma implementação do padrão de fábrica?
Dennis
1
@ Dennis: Como um caso degenerado, acho que sim. O problema Factoryé que é bastante genérico e cobre muito terreno; uma fábrica pode adicionar argumentos (dependendo do ambiente / configuração) ou fornecer algum cache (relacionado ao Flyweight / Pools), por exemplo, mas esses casos só fazem sentido em algumas situações.
Matthieu M.
Se apenas mudando o compilador seria tão fácil como você faz parecer :)
rozina
@rozina: :) Funciona bem no Linux (o gcc / clang é notavelmente compatível); Admito que o Windows ainda está relativamente fechado, embora deva melhorar na plataforma de 64 bits (menos patentes no caminho, se bem me lembro).
Matthieu M.
E então você tem todo o mundo incorporado com alguns compiladores abaixo da média. :) Estou trabalhando com um desses que não possui otimização de valor de retorno. Eu gostaria que tivesse embora. Infelizmente, mudar não é uma opção no momento. Espero que no futuro ele será atualizado ou vamos fazer um interruptor para sth mais :)
rozina