Pregunta

He estado desarrollando una biblioteca de GUI para Windows (como un proyecto paralelo personal, sin aspiraciones de utilidad). Para mi clase de ventana principal, he configurado una jerarquía de clases de opciones (usando Idioma del parámetro con nombre ), porque algunas opciones se comparten y otras son específicas para determinados tipos de ventanas (como diálogos).

La forma en que funciona el nombre de parámetro con nombre, las funciones de la clase de parámetro tienen que devolver el objeto al que se llama. 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 opción. Aquí hay 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 puede ver, requiere una cantidad considerable de trabajo adicional: una conversión de tipos en el tipo de devolución para cada función, clases de plantillas adicionales, etc. ”.

Mi pregunta es, ¿existe una forma más fácil de implementar el idioma de los parámetros con nombre en este caso, uno que no requiera todas las cosas adicionales?

¿Fue útil?

Solución

Tal vez no sea lo que quieres escuchar, pero por mi parte creo que está bien tener muchos tipos de letra y parámetros de plantilla feos en el código de la biblioteca que está (más o menos) oculto para 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 tú y parte del código se pone muy feo, pero al final me permite escribir un código de cliente hermoso (al menos en mis ojos (posiblemente pervertidos) y eso es lo que cuenta IMHO.

Otros consejos

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

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

Entonces, en tu ejemplo, harías algo como

Ventana ventana = CreateWindow (" foo "). menu (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.

No sé si estoy enamorado de esta respuesta, pero existe la posibilidad de usar la deducción de argumentos de plantilla. NOTA No tengo mi compilador, lo revisaré 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; };
 }

Entonces llamarías a CreateWindow así:

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

Lo desagradable de esto, por supuesto, es tener que usar la sintaxis de llamadas al método estático y todos los paréntesis adicionales. Si reemplaza las funciones miembro estáticas con funciones no miembros, esto puede eliminarse. Sin embargo, evita la conversión de tipos y las clases de plantillas adicionales.

Personalmente, prefiero tener el código impar en la biblioteca como con tu método, que en todos los lugares donde se usa la biblioteca como en la mía.

Las plantillas están calientes.

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

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

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

//////// 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 los parámetros para mostrar que pueden poner en el orden que se desee.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top