Лучший Способ Использовать Идиому Именованных Параметров C++?
Я разрабатывал библиотеку GUI для Windows (как личный сайд-проект, без стремления к полезности). Для моего класса главного окна я установил иерархию классов опций (используя идиому именованных параметров ), потому что некоторые опции являются общими, а другие специфичны для определенных типов окон (например, диалогов).
Как работает идиома именованного параметра, функции класса parameter должны возвращать объект, на который они вызываются. Проблема заключается в том, что в иерархия, каждый из них должен быть различным классом - createWindowOpts
классом для стандартных окон, createDialogOpts
классом для диалогов и т. д. Я справился с этим, сделав все классы опций шаблонами. Вот пример:
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() { };
};
Это работает, но, как вы можете видеть, это требует заметного количества дополнительной работы: приведение типа к возвращаемому типу для каждой функции, дополнительные классы шаблонов и т. д.
Мой вопрос в том, есть ли более простой способ реализовать идиому именованного параметра в это дело, которое не требует всего лишнего?
6 ответов:
Может быть, не то, что вы хотите услышать, но я, например, думаю, что это нормально иметь много уродливых приведений типов и параметров шаблона в коде библиотеки, который (более или менее) скрыт от клиента до тех пор, пока это безопасно и делает жизнь клиента намного проще. Красота библиотечного кода заключается не в самом коде, а в том коде, который он позволяет клиентам писать. Взять к примеру стл.
Я также разработал небольшую GUI-библиотеку в качестве личного проекта с практически тем же самым стремления, как вы и некоторые из кода, становятся довольно уродливыми в нем, но в конце концов это позволяет мне писать красивый клиентский код (по крайней мере, в моих (возможно, извращенных) глазах), и это то, что считается IMHO.
Как насчет...?
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(); }; // ! // ... };
Не могли бы вы просто связать вызовы метода в обратном порядке наследования?
Итак, в вашем примере вы бы сделали что-то вроде
Окно = функции createwindow("фу").меню(hmenu).владелец(его hwnd).в(0,0).фон(евр.);
Я понимаю, что это не 100% прозрачно, но кажется немного проще и почти правильно.
Я не знаю, нравится ли мне этот ответ, но вот возможность использования дедукции аргументов шаблона. примечание у меня нет с собой моего компилятора, я перепроверю его завтра, если только кто-то еще не захочет его раскрутить.
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; }; }
Тогда вы вызовете CreateWindow следующим образом:
CreateWindow( createWindowOpts::owner(hwnd, createWindowOpts::at(0, 100, // can use createWindowOpts because it doesn't hide sharedWindowsOpts::at createWindowOpts::menu(hmenu, createWindowOpts() ) ) ) );
Самое неприятное в этом, конечно, то, что приходится использовать синтаксис вызова статического метода и все дополнительные скобки. Если вы замените статические функции-члены с функциями, не являющимися членами, Это можно устранить. Однако он избегает приведения типов и дополнительных классов шаблонов.
Лично я предпочел бы иметь странный код в библиотеке, как в вашем методе, чем везде, где библиотека используется, как в моем.
Шаблоны горячие.
Но POP (обычный старый полиморфизм) не мертв.Почему бы не вернуть (умный)указатель на подкласс?
Я знаю, что опоздал на год и у меня не хватает доллара, но я все равно внесу свое решение.
//////// 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 происходит от B, А B-от A. я повторил параметры, чтобы показать, что они могут располагаться в любом желаемом порядке.