Better Way To Use C++ Named Parameter Idiom?
我一直在为Windows开发一个GUI库(作为一个个人项目,没有有用的愿望)。对于我的主窗口类,我已经设置了选项类的层次结构(使用命名参数惯用法),因为有些选项是共享的,而其他选项是特定于特定类型的窗口(如对话框)。
命名参数习惯用法的工作方式是,参数类的函数必须返回调用它们的对象。问题是,在层次结构中,每个类都必须是不同的类——标准窗口的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | 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() { }; }; |
它可以工作,但正如您所看到的,它需要大量的额外工作:为每个函数在返回类型上强制转换类型、额外的模板类等。
我的问题是,在这种情况下,是否有一种更容易实现命名参数习语的方法,不需要所有额外的东西?
也许不是你想听到的,但我个人认为在库代码中有许多丑陋的类型转换和模板参数(或多或少)对客户机隐藏是可以的,只要它是安全的,并使客户机的生活更容易。库代码中的美不在代码本身,而是在代码中,它使客户机能够编写代码。以STL为例。
我还开发了一个小的图形用户界面库作为一个个人项目,基本上和你有着相同的愿望,其中一些代码变得非常难看,但最终它允许我编写漂亮的客户端代码(至少在我(可能是变态的)眼睛里),这就是为什么我要写。
怎么样。。。?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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(); }; // ! // ... }; |
你能不能用继承的相反顺序来链接方法调用?
所以在你的例子中,你会做一些
window window=createwindow("foo").menu(hmenu).owner(hwnd).at(0,0).background(hbr);
我意识到它不是100%透明的,但看起来有点简单,几乎是正确的。
我不知道我是否喜欢这个答案,但这里有一个使用模板参数推导的可能性。注意:我身上没有编译器,明天我会仔细检查它,除非外面有人想试一试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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:
1 2 3 | CreateWindow( createWindowOpts::owner(hwnd, createWindowOpts::at(0, 100, // can use createWindowOpts because it doesn't hide sharedWindowsOpts::at createWindowOpts::menu(hmenu, createWindowOpts() ) ) ) ); |
当然,这方面令人讨厌的事情是必须使用静态方法调用语法和所有额外的括号。如果将静态成员函数替换为非成员函数,则可以消除此问题。不过,它确实避免了类型转换和额外的模板类。
就我个人而言,我宁愿把奇怪的代码放在库中,就像你的方法一样,而不是像我的方法一样,把库用在任何地方。
我知道我晚了一年又缺钱,但无论如何我都会全力解决。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | //////// 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 create() { shared_ptr 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 = 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派生的。我重复了一些参数,以表明它们可以按任意顺序排列。
模板很热。
但是pop(简单的老多态性)并没有死。
为什么不向子类返回一个(智能)指针呢?