2008-10-17 4 views
9

He estado desarrollando una biblioteca GUI para Windows (como un proyecto lateral personal, sin aspiraciones de utilidad). Para mi clase de ventana principal, he configurado una jerarquía de clases de opciones (usando el Named Parameter Idiom), porque algunas opciones se comparten y otras son específicas para determinados tipos de ventanas (como los cuadros de diálogo).¿Mejor manera de usar el lenguaje personalizado de parámetros de C++?

La manera en que funciona el parámetro Named Parameter, las funciones de la clase de parámetro tienen que devolver el objeto al que están llamados. El problema es que, en la jerarquía, cada uno tiene que ser una clase diferente - la clase createWindowOpts para ventanas estándar, la clase createDialogOpts para diálogos y similares. He tratado con eso haciendo todas las plantillas de clases de opciones. He aquí un ejemplo:

template <class T> 
class _sharedWindowOpts: public detail::_baseCreateWindowOpts { 
    public: /////////////////////////////////////////////////////////////// 
    // No required parameters in this case. 
    _sharedWindowOpts() { }; 

    typedef T optType; 

    // Commonly used options 
    optType& at(int x, int y) { mX=x; mY=y; return static_cast<optType&>(*this); }; // Where to put the upper-left corner of the window; if not specified, the system sets it to a default position 
    optType& at(int x, int y, int width, int height) { mX=x; mY=y; mWidth=width; mHeight=height; return static_cast<optType&>(*this); }; // Sets the position and size of the window in a single call 
    optType& background(HBRUSH b) { mBackground=b; return static_cast<optType&>(*this); }; // Sets the default background to this brush 
    optType& background(INT_PTR b) { mBackground=HBRUSH(b+1); return static_cast<optType&>(*this); }; // Sets the default background to one of the COLOR_* colors; defaults to COLOR_WINDOW 
    optType& cursor(HCURSOR c) { mCursor=c; return static_cast<optType&>(*this); }; // Sets the default mouse cursor for this window; defaults to the standard arrow 
    optType& hidden() { mStyle&=~WS_VISIBLE; return static_cast<optType&>(*this); }; // Windows are visible by default 
    optType& icon(HICON iconLarge, HICON iconSmall=0) { mIcon=iconLarge; mSmallIcon=iconSmall; return static_cast<optType&>(*this); }; // Specifies the icon, and optionally a small icon 
    // ...Many others removed... 
}; 

template <class T> 
class _createWindowOpts: public _sharedWindowOpts<T> { 
    public: /////////////////////////////////////////////////////////////// 
    _createWindowOpts() { }; 

    // These can't be used with child windows, or aren't needed 
    optType& menu(HMENU m) { mMenuOrId=m; return static_cast<optType&>(*this); }; // Gives the window a menu 
    optType& owner(HWND hwnd) { mParentOrOwner=hwnd; return static_cast<optType&>(*this); }; // Sets the optional parent/owner 
}; 

class createWindowOpts: public _createWindowOpts<createWindowOpts> { 
    public: /////////////////////////////////////////////////////////////// 
    createWindowOpts() { }; 
}; 

Funciona, pero como se puede ver, se requiere una notable cantidad de trabajo extra: un tipo de fundición del tipo de cambio de cada función, clases de plantilla adicionales, etcétera.

Mi pregunta es, ¿hay alguna manera más fácil de implementar el idioma de parámetros nominales en este caso, uno que no requiera todo el material extra?

Respuesta

9

Tal vez no lo que quiere oír, pero por mi parte, creo que está bien tener un montón de feas tipo-moldes y parámetros de plantilla en la biblioteca de código que está (más o menos) oculto el cliente , siempre y cuando es seguro y hace que la vida del cliente sea mucho más fácil. La belleza en el código de la biblioteca no está en el código en sí, pero en el código permite a los clientes escribir. Tome STL por ejemplo.

También he desarrollado una pequeña biblioteca de GUI como un proyecto personal con básicamente las mismas aspiraciones que usted y parte del código se pone bastante feo en él, pero al final me permite escribir un hermoso código de cliente (en al menos en mis ojos (posiblemente pervertidos)) y eso es lo que cuenta en mi humilde opinión.

+1

Estoy de acuerdo, me preguntaba si alguien sabía de una forma más agradable de hacer las cosas en el código de la biblioteca. Gracias por el aporte. –

2

¿Podría simplemente encadenar las llamadas al método por orden inverso de la herencia?

Así que en su ejemplo que harías algo así como

ventana Ventana = CreateWindow menú ("foo") (hMenu) .owner (hwnd) .at (0,0) .background (HBr).;

Me doy cuenta de que no es 100% transparente, pero parece un poco más fácil y casi correcto.

+0

Concepto interesante. No lo había considerado antes, pero debería funcionar. Como dijiste, no es perfecto: el usuario de la biblioteca tendría que recordar en qué orden hacer las llamadas, lo que no debería hacer, pero está cerca. –

1

No sé si estoy enamorado de esta respuesta, pero esta es una posibilidad usando la deducción de argumento de la plantilla. NOTA No tengo mi compilador en mi cuenta, lo verificaré mañana a menos que alguien más quiera darle un giro.

class sharedWindowOpts 
{ 
public: 

    sharedWindowOpts() {}; 

    // Commonly used options 
    template <class optType> 
    static optType& at(int x, int y, optType& opts) { opts.mX=x; opts.mY=y; return opts; }; 

    template <class optType> 
    static optType& background(HBRUSH b, optType& opts) { opts.mBackground=b; return opts; }; 

    // etc... 
} 

class createWindowOpts : public sharedWindowOpts 
{ 
public: 
    createWindowOpts() : sharedwindowOpts() {}; 

    // These can't be used with child windows, or aren't needed 
    template <class optType> 
    static optType& menu(HMENU m, optType& opts) { opts.mMenuOrId=m; return opts; }; 

    template <class optType> 
    static optType& owner(HWND hwnd, optType& opts) { opts.mParentOrOwner=hwnd; return opts; }; 
} 

allí tendría que llamar CreateWindow así:

CreateWindow(createWindowOpts::owner(hwnd, 
       createWindowOpts::at(0, 100,  // can use createWindowOpts because it doesn't hide sharedWindowsOpts::at 
       createWindowOpts::menu(hmenu, createWindowOpts())))); 

Las cosas desagradables acerca de esto, por supuesto, están teniendo que utilizar el método estático llamar sintaxis y todos los paréntesis adicionales. Si reemplaza las funciones de miembro estático con funciones que no son miembros, esto puede eliminarse. Sin embargo, evita las clases de moldeado de texto y la plantilla adicional.

Personalmente, prefiero tener el código impar en la biblioteca como con su método, que en todas partes donde la biblioteca se está utilizando como en la mía.

+0

Tengo que estar de acuerdo con su último comentario, es mejor tener las cosas feas escondidas en la biblioteca que hacer que el usuario de la biblioteca se ocupe de ellas. –

5

¿Qué tal ...?

template <class T> 
class _sharedWindowOpts: public detail::_baseCreateWindowOpts { 

protected: // (protected so the inheriting classes may also use it) 

    T & me() { return static_cast<T&>(*this); }    // ! 

public: 
    // No required parameters in this case. 
    _sharedWindowOpts() { }; 

    typedef T optType; 

    // Commonly used options 
    optType& at(int x, int y) { mX=x; mY=y; return me(); }; // ! 
    // ... 
}; 
+0

Esto al menos oculta muy bien la transmisión estática. –

+0

Ya había planeado incorporar eso, pero gracias por mencionarlo. No estoy seguro de por qué no pensé en eso antes de comenzar a escribir el código. –

0

Las plantillas están calientes.

Pero POP (Polimorfismo viejo y llano) no está muerto.

¿Por qué no se devuelve un puntero (inteligente) a la subclase?

+0

Devuelve una referencia a la subclase con return static_cast (* this). La plantilla le impide tener que hacer una nueva versión del método para cada subclase. –

+0

Exactamente. Me gustaría * evitar * las plantillas aquí, pero son necesarias aquí para que el valor devuelto pueda ser del tipo correcto. De eso se trataba en realidad esta pregunta. –

0

Sé que tengo un año de retraso y un dólar corto, pero voy a lanzar en mi solución de todos modos.

//////// Base.. 

template<typename DerivedBuilder, typename Options> 
class Builder 
{ 
protected: 
    Builder() {} 
    DerivedBuilder& me() { return *static_cast<DerivedBuilder*>(this); } 

    Options options; 
}; 


//////////////////////////  A  ////////////////////////// 


class Options_A 
{ 
public: 
    Options_A() : a(7) {} 
    int a; 
}; 

class Builder_A; 

class A 
{ 
public: 
    virtual ~A() {} 
    virtual void print() { cout << "Class A, a:" << a << endl; } 

protected: 
    friend class Builder_A; 
    A(const Options_A& options) : a(options.a) {} 
    int a; 
}; 



template<typename DerivedBuilder, typename Options = Options_A> 
class BuilderT_A : public Builder<DerivedBuilder, Options> 
{ 
public: 
    using Builder<DerivedBuilder, Options>::options; 
    using Builder<DerivedBuilder, Options>::me; 
    DerivedBuilder& a(int p) { options.a = p; return me(); } 
}; 


class Builder_A : public BuilderT_A<Builder_A> 
{ 
public: 
    shared_ptr<A> create() 
    { 
     shared_ptr<A> obj(new A(options)); 
     return obj; 
    } 
}; 

//////////////////////////  B  ////////////////////////// 



class Options_B : public Options_A 
{ 
public: 
    Options_B() : b(8) {} 
    int b; 
}; 

class Builder_B; 

class B : public A 
{ 
public: 
    virtual ~B() {} 
    virtual void print() { cout << "Class B, a:" << a << ", b:" << b << endl; } 

protected: 
    friend class Builder_B; 
    B(const Options_B& options) : A(options), b(options.b) {} 
    int b; 
}; 


template<typename DerivedBuilder, typename Options = Options_B> 
class BuilderT_B : public BuilderT_A<DerivedBuilder, Options> 
{ 
public: 
    using Builder<DerivedBuilder, Options>::options; 
    using Builder<DerivedBuilder, Options>::me; 
    DerivedBuilder& b(int p) { options.b = p; return me(); } 
}; 


class Builder_B : public BuilderT_B<Builder_B> 
{ 
public: 
    shared_ptr<B> create() 
    { 
     shared_ptr<B> obj(new B(options)); 
     return obj; 
    } 
}; 



//////////////////////////  C  ////////////////////////// 



class Options_C : public Options_B 
{ 
public: 
    Options_C() : c(9) {} 
    int c; 
}; 

class Builder_C; 

class C : public B 
{ 
public: 
    virtual ~C() {} 
    virtual void print() { cout << "Class C, a:" << a << ", b:" << b << ", c:" << c << endl; } 

protected: 
    friend class Builder_C; 
    C(const Options_C& options) : B(options), c(options.c) {} 
    int c; 
}; 


template<typename DerivedBuilder, typename Options = Options_C> 
class BuilderT_C : public BuilderT_B<DerivedBuilder, Options_C> 
{ 
public: 
    using Builder<DerivedBuilder, Options>::options; 
    using Builder<DerivedBuilder, Options>::me; 
    DerivedBuilder& c(int p) { options.c = p; return *static_cast<DerivedBuilder*>(this); } 
}; 


class Builder_C : public BuilderT_C<Builder_C> 
{ 
public: 
    shared_ptr<C> create() 
    { 
     shared_ptr<C> obj(new C(options)); 
     return obj; 
    } 
}; 





/////////////////////////////////////////////////////////////////////////// 


int main() 
{ 
    shared_ptr<A> a = Builder_A().a(55).a(1).create(); 
    a->print(); 

    shared_ptr<B> b = Builder_B().b(99).b(2).a(88).b(4).a(2).b(3).create(); 
    b->print(); 

    shared_ptr<C> c = Builder_C().a(99).b(98).c(97).a(96).c(6).b(5).a(4).create(); 
    c->print(); 

    return 0; 
} 

/* Output: 

Class A, a:1 
Class B, a:2, b:3 
Class C, a:4, b:5, c:6 

*/ 

C se deriva de B, y B se deriva de A. He repetido parámetros para mostrar que pueden poner en cualquier orden deseado.

+0

Parece interesante. Tendré que estudiarlo más a fondo en algún momento ... en este momento, estoy en medio de otros dos proyectos y no puedo perdonarle nada a las células cerebrales. :-) –

Cuestiones relacionadas