使用C++命名参数熟语的更好方法?

Better Way To Use C++ Named Parameter Idiom?

我一直在为Windows开发一个GUI库(作为一个个人项目,没有有用的愿望)。对于我的主窗口类,我已经设置了选项类的层次结构(使用命名参数惯用法),因为有些选项是共享的,而其他选项是特定于特定类型的窗口(如对话框)。

命名参数习惯用法的工作方式是,参数类的函数必须返回调用它们的对象。问题是,在层次结构中,每个类都必须是不同的类——标准窗口的createWindowOpts类、对话框的createDialogOpts类等等。我通过制作所有选项类模板来处理这个问题。下面是一个例子:

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(简单的老多态性)并没有死。

为什么不向子类返回一个(智能)指针呢?