Лучший способ использования именованного параметра C ++? - PullRequest
9 голосов
/ 17 октября 2008

Я занимаюсь разработкой библиотеки графического интерфейса для Windows (в качестве личного проекта, без надежды на полезность). Для моего класса основного окна я установил иерархию классов параметров (используя Именованный элемент именованных параметров ), поскольку некоторые параметры являются общими, а другие специфичны для определенных типов окон (например, диалогов). 1003 *

Как работает именованный параметр, функции класса параметров должны возвращать объект, для которого они вызваны. Проблема состоит в том, что в иерархии каждый должен быть другим классом - 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 ]

9 голосов
/ 17 октября 2008

Может быть, не то, что вы хотите услышать, но я думаю, что нормально иметь много уродливых приведений типов и параметров шаблона в библиотечном коде, который (более или менее) скрыт от клиента , пока это безопасно и значительно облегчают жизнь клиента. Прелесть в коде библиотеки не в самом коде, а в коде, который позволяет клиентам писать. Возьмите STL например.

Я также разработал небольшую GUI-библиотеку в качестве личного проекта, в основном с теми же стремлениями, что и у вас, и часть кода становится довольно уродливой, но в конце она позволяет мне писать красивый клиентский код в моих (возможно, извращенных) глазах) и это то, что считается ИМХО.

5 голосов
/ 17 октября 2008

Как насчет ...?

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(); };   // !
    // ...
};
2 голосов
/ 17 октября 2008

Не могли бы вы просто объединить вызовы методов в обратном порядке наследования?

Итак, в вашем примере вы бы сделали что-то вроде

Окно окна = CreateWindow ("foo"). Menu (hmenu) .owner (hwnd) .at (0,0) .background (hbr);

Я понимаю, что это не на 100% прозрачно, но кажется немного легче и почти правильно.

1 голос
/ 17 октября 2008

Я не знаю, влюблен ли я в этот ответ, но есть возможность с использованием вывода аргументов шаблона. ПРИМЕЧАНИЕ У меня нет моего компилятора, завтра я проверю его дважды, если кто-то еще не захочет его обернуть.

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() ) ) ) );

Несносные вещи об этом, конечно, должны использовать статический метод, вызывающий синтаксис и все лишние скобки. Если вы замените статические функции-члены функциями, не являющимися членами, это можно устранить. Тем не менее, он избегает приведения типов и дополнительных шаблонных классов.

Лично я предпочел бы иметь нечетный код в библиотеке, как в вашем методе, чем везде, где используется библиотека, как в моем.

0 голосов
/ 11 мая 2010

Я знаю, что опаздываю на год и у меня не хватает доллара, но я все равно предложу свое решение.

//////// 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. Я повторил параметры, чтобы показать, что они могут устанавливать в любом порядке.

0 голосов
/ 17 октября 2008

Шаблоны горячие.

Но POP (Простой старый полиморфизм) не умер.

Почему бы не вернуть (умный) указатель на подкласс?

...