todas - ¿Mejor manera de usar el lenguaje personalizado de parámetros de C++?
tipos de funciones en c (6)
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 la expresión de parámetros nombrados ), porque algunas opciones se comparten y otras son específicas para determinados tipos de ventanas (como los diálogos).
La forma en que funciona el Lenguaje de parámetros nominales, 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. 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 notable cantidad de trabajo extra: un tipo de conversión en el tipo de retorno para cada función, clases de plantillas adicionales, etcétera.
Mi pregunta es, ¿hay alguna manera más fácil de implementar el idioma de los parámetros nombrados en este caso, uno que no requiera todo el material extra?
Qué tal si...?
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 encadenar las llamadas al 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.
Las plantillas están calientes.
Pero POP (Polimorfismo viejo y llano) no está muerto.
¿Por qué no devuelve un puntero (inteligente) a la subclase?
No sé si estoy enamorado de esta respuesta, pero esta es una posibilidad usando la deducción de argumentos de la plantilla. NOTA : No tengo mi compilador, lo comprobaré 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 llamada del método estático 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 de plantillas adicionales.
Personalmente, prefiero tener el código impar en la biblioteca como con su método, que en todas partes en que se usa la biblioteca como en la mía.
Tal vez no sea lo que quieres escuchar, pero por mi parte, creo que está bien tener muchos tipos feos y parámetros de plantilla en el código de la biblioteca que está (más o menos) oculto para el cliente , siempre y cuando sea seguro y haga la vida del cliente 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 desarrollé una pequeña biblioteca de GUI como un proyecto personal con básicamente las mismas aspiraciones que tú y parte del código se vuelve bastante feo, pero al final me permite escribir un hermoso código de cliente (al menos en mi ( posiblemente pervertido) ojos) y eso es lo que cuenta en mi humilde opinión.
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 deriva de B y B deriva de A. He repetido los parámetros para mostrar que pueden poner en el orden deseado.