Selecionando entre dois construtores

Problema: Eu tenho um object não copiável com dois construtores. Eu preciso criar um object com um dos construtores e usá-lo dentro de algum código comum:

Com um object copiável, ficaria assim e seria fácil:

Object a; if (condition) a = Object(p1); else a = Object(p2,p3,p4); a.doSomething(); 

Mas, o object não é copiável, então eu tive que fazer isso:

 boost::scoped_ptr a; if (condition) a = new Object(p1); else a = new Object(p2,p3,p4); a->doSomething(); 

Isso parece muito complexo. Existe uma solução melhor?

Aqui está um hack muito terrível, assumindo que Object é construtível por padrão:

 Object a; a.~Object(); if (condition) { ::new (&a) Object(p1); } else { ::new (&a) Object(p2, p3, p4); } 

Não use isso.

Outra opção é usar uma união, mas você precisará invocar o destruidor manualmente nessa configuração também.


Uma solução mais limpa poderia ser alcançada com Boost.Optional (usando fábricas no local ). (Obrigado ao @K-Ballo por desenterrar os detalhes!)

 #include  #include  struct Object { explicit Object(int) {} explicit Object(int, float, std::string) {} Object(Object const &) = delete; Object(Object &&) = delete; Object & operator=(Object const &) = delete; Object & operator=(Object &&) = delete; }; boost::optional a; if (condition) { a = boost::in_place(0); } else { a = boost::in_place(0, 1.0f, "two" ); } 

Parece perfeitamente razoável para mim do jeito que é. É claro, simples e relativamente conciso.

 auto const doSomethingTo = []( Object&& o ) { o.doSomething(); }; doSomethingTo( condition? Object( p1 ) : Object( p1, p2, p3 ) ); 

Disclaimer: código não tocado pelo compilador.


EDIT : o código acima, quando o construtor Object( Object&& ) é private , falha ao compilar com o MSVC 11.0 (sim, mesmo no CTP de novembro do ano passado), mas compila bem com o MinGW g ++ 4.7.1 e com alguma versão do clang .

Parece que deve compilar .

Então, é provavelmente um bug no Visual C ++ – mas infelizmente não encontrei uma solução fácil.


Uma solução difícil para o erro do compilador Visual C ++ presumido:

 #include  #include  using namespace std; class Object { private: Object( Object const& ); Object( Object&& ); public: void doSomething() const {} Object( int ) {} Object( int, int, int ) {} }; int main( int argc, char* argv[] ) { int p1 = 0, p2 = 0, p3 = 0; bool condition = argc == 2; auto const doSomething1 = [=]() { Object o( p1 ); o.doSomething(); }; auto const doSomething2 = [=]() { Object o( p1, p2, p3 ); o.doSomething(); }; if( condition ) { doSomething1(); } else { doSomething2(); } } 

Outra resposta afirma que o new (leia-se: uma alocação dinâmica) é sua única opção.

Isto é errado.

Não há realmente nada de errado com a sua solução, embora, como outros já mencionaram, seria mais legível se você usasse o operador condicional em vez de se fosse. Mas você deve considerar a possibilidade de refatoração. Se você fatorar todo o código que usa o object em uma function separada (tomando o object por referência), então algo como:

 if ( condition ) { Object a( p1 ); doWhatever( a ); } else { Object a( p2, p3, p4 ); doWhatever( a ); } 

pode ser preferível (ou não – não acho que haja uma resposta “certa” em relação à escolha entre esses dois).

Eu não vejo a complexidade … Se você precisa construir eficientemente com base em uma condição se declarar um ponteiro e usar o novo é sua única opção. O que você não precisa necessariamente fazer é:

  1. Use um scoped_ptr (embora geralmente seja uma boa ideia)
  2. Ter o construtor no if no seu código “principal”. O seu é um caso de uso típico para uma fábrica (consulte, por exemplo, http://en.wikipedia.org/wiki/Factory_method_pattern ).

EDIT: adicionado “eficientemente” na segunda frase.

Eu acho que seu código está OK.

Você só pode querer considerar o operador condicional ? : ? :

 boost::scoped_ptr a( condition ? new Object(p1) : new Object(p2,p3,p4) ); a->doSomething(); 

Então, aqui está um truque rápido para fazer isso funcionar, sem construir objects manualmente. Em vez disso, criei um modelo Deferred que representa um object no armazenamento automático cuja construção é adiada (e possivelmente nunca ocorre).

O buff em Deferred deve ser substituído por uma union , porque isso tratará de problemas de alinhamento (supondo que você tenha resources do C ++ 11 para suportar isso). Afirma que a constructed é verdadeira quando você chama get() é provavelmente uma boa idéia.

Uma vez que você construiu seu object, você pode implicitamente converter seu Deferred para um T& , então usar esse T& como um alias para o T construído deferido.

Teoricamente, você poderia acabar com o bool constructed se pudesse provar que ele seria sempre construído, mas eu aconselharia contra ele. Fora isso, isso deve ser quase tão eficiente quanto você conseguir. E com o caso da union C ++ 11, pode até ser compatível com os padrões.

Ah, sim, e isso deve ser aprimorado com um encaminhamento perfeito.

 #include  // does not handle alignment issues: template struct Deferred { Deferred():constructed(false) {} operator T&() { return get(); } T& get() { return *reinterpret_cast(&buff[0]); } template T& construct( Args... args ) { new(&buff[0]) T(args...); constructed = true; return get(); } ~Deferred() { if (constructed) { get().~T(); } } private: bool constructed; char buff[sizeof(T)]; }; #include  struct Object { bool is_int; Object( int x ):is_int(true) {} Object( double d ):is_int(false) {} ~Object() { std::cout << "~Object("< o; if(v==as_int) { o.construct( 7 ); } else if (v==as_double) { o.construct( 7.0 ); } else { } } int main() { test(as_int); test(as_double); test(do_not); }