Переносимость нативных свойств C ++ - PullRequest
27 голосов
/ 24 апреля 2011

В Visual Studio есть __declspec(property), который создает свойства, подобные C #. Borland C ++ предлагает ключевое слово __property с точно такой же функциональностью. В C ++ 0x упоминается ключевое слово implicit, которое может быть расширено для реализации той же функциональности. Но это не вошло в спецификацию.

Я ищу портативный и относительно чистый метод объявления синтаксически зашитых свойств, который будет компилироваться в последних компиляторах для Windows, OSX и Linux. Меня не интересует совместимость компилятора, только один компилятор на платформу.

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

Вот идеальное использование, которое компилируется в Visual Studio 2010:

#define _property(_type, _name, _get, _put) __declspec(property(get=_get, put=_put)) _type _name
#define _property_readonly(_type, _name, _get) __declspec(property(get=_get)) _type _name

class Window
{
public:
    _property_readonly(void*, Handle, GetHandle);
    _property(bool, Visible, GetVisible, SetVisible);

    void* GetHandle();
    bool GetVisible();
    void SetVisible(bool);
}

void main()
{
    Window MainWindow;
    if (!MainWindow.Visible)
        MainWindow.Visible = true;
}

Ответы [ 4 ]

26 голосов
/ 08 мая 2011

Это что-то похожее на то, что вы спрашиваете, и (я надеюсь) стандарт C ++ ...

#include <iostream>

template<typename C, typename T, T (C::*getter)(), void (C::*setter)(const T&)>
struct Property
{
    C *instance;

    Property(C *instance)
        : instance(instance)
    {
    }

    operator T () const
    {
        return (instance->*getter)();
    }

    Property& operator=(const T& value)
    {
        (instance->*setter)(value);
        return *this;
    }

    template<typename C2, typename T2,
             T2 (C2::*getter2)(), void (C2::*setter2)(const T2&)>
    Property& operator=(const Property<C2, T2, getter2, setter2>& other)
    {
        return *this = (other.instance->*getter2)();
    }

    Property& operator=(const Property& other)
    {
        return *this = (other.instance->*getter)();
    }
};

//////////////////////////////////////////////////////////////////////////

struct Foo
{
    int x_, y_;

    void setX(const int& x) { x_ = x; std::cout << "x new value is " << x << "\n"; }
    int getX() { std::cout << "reading x_\n"; return x_; }

    void setY(const int& y) { y_ = y; std::cout << "y new value is " << y << "\n"; }
    int getY() { std::cout << "reading y_\n"; return y_; }

    Property<Foo, int, &Foo::getX, &Foo::setX> x;
    Property<Foo, int, &Foo::getY, &Foo::setY> y;

    Foo(int x0, int y0)
        : x_(x0), y_(y0), x(this), y(this)
    {
    }
};

int square(int x)
{
    return x*x;
}

int main(int argc, const char *argv[])
{
    Foo foo(10, 20);
    Foo foo2(100, 200);
    int x = foo.x; std::cout << x << "\n";
    int y = foo.y; std::cout << y << "\n";
    foo.x = 42; std::cout << "assigned!\n";
    x = foo.x; std::cout << x << "\n";
    std::cout << "same instance prop/prop assign!\n";
    foo.x = foo.y;
    std::cout << "different instances prop/prop assign\n";
    foo.x = foo2.x;
    std::cout << "calling a function accepting an int parameter\n";
    std::cout << "square(" << foo.x << ") = " <<  square(foo.x) << "\n";
    return 0;
}

Как вы можете видеть из main использование прозрачно, пока вы назначаетезначения типа T (здесь int) или неявно преобразуемые в T свойства и до тех пор, пока вы преобразуете их обратно в T значения при чтении.

Поведение будет другим, однако, если вынапример, передайте foo.x в шаблонную функцию, потому что тип foo.x не int, а Property<Foo, int, ...>.

У вас также могут быть проблемы с не шаблонными функциями ... вызов функциипринятие значения T будет работать нормально, однако параметр T&, например, будет проблемой, потому что в основном функция запрашивает переменную для прямого доступа с использованием адреса.По той же причине вы не можете, конечно, передать адрес свойства функции, принимающей параметр T*.

7 голосов
/ 18 апреля 2017

Clang теперь полностью реализован на Microsoft __declspec(property...), и он прекрасно оптимизирует. Таким образом, вы можете использовать свойства в вашем c ++ для всех платформ и смешивать в коде на основе gcc или в коде c99 и т. Д.

Я пользуюсь им более года и ждал, пока он появится повсеместно более пяти лет.

Это один из самых мощных инструментов C ++ для абстрагирования структуры и рефакторинга кода. Я использую его все время, чтобы позволить мне быстро построить структуру, а затем реорганизовать ее позже, когда этого потребует производительность или реструктуризация.

Это бесценно, и я действительно не понимаю, почему стандарты C ++ не приняли его давно. Но опять же, у них так много сложного и раздутого boost способа использования c ++ и шаблонов.

Clang теперь настолько переносим для любой платформы, что обладать этой функцией просто фантастика.

Разработка в рамках (бесплатная или платная версия) Visual Studio с использованием clang практически без шва, и вы получаете невероятный набор инструментов для разработки отладки, который просто сравнивает работу с другими наборами инструментов и платформ.

Сейчас я использую исключительно clang для всех моих разработок на c ++.

3 голосов
/ 08 мая 2011

Ищу портативный и относительно чистый метод декларирования синтаксически засахаренные свойства, которые скомпилирует в последних компиляторах для Windows, OSX и Linux.

Вы описываете возможности типа «мета-объект», такие как свойства, определенные во время компиляции или во время выполнения, такие как те, которые могут быть иначе реализованы через «бобы Java» или «отражение .NET», или любое количество способы работы с высокоуровневыми языками сценариев, такими как Python и Perl.

Например, то, что вы описываете (свойства времени компиляции и / или времени выполнения), реализовано в библиотеках Qt (C ++) через QMetaObject. Вы можете создать его экземпляр напрямую, использовать его в качестве «члена» в своих классах или получить от QObject для «автоматического» получения поведения мета-объекта (и некоторых других вещей, например, помогает «приведение типов» и перекрестные сигналы / слоты -потоки). Конечно, они довольно кроссплатформенные (например, Win, Mac, Posix).

Я не большой поклонник использования __declspec(), за исключением очень специфичного для платформы использования, такого как явный экспорт типов через «Microsoft Extension DLL» (которого я обычно стараюсь избегать, если это возможно). Я не думаю, что есть какой-либо способ сделать такое использование "кросс-платформенным" (поскольку это конкретное использование специфично для библиотек MS DLL).

Точно так же не составит труда написать собственный класс типа "MyMetaObject", который по сути является "словарем", "хешем" или "ассоциативным массивом", которые используются вашими объектами и которые динамически заполняются. во время выполнения, даже с вашими внутренними типами (такими как MyColor, MyTime, MyFilePath и т. д.), я делал это несколько раз, и это не должно быть много работы, и это может работать довольно элегантно. (QMetaObject, как правило, немного более мощный, чем эти простые подходы, но он требует этапа компиляции "moc", который является очень мощным шагом для генерации кода быстрого поиска для его свойств и включения сигналов / слотов ).

Наконец, вы начинаете слегка касаться домена «Динамический C ++», что подразумевает более легкое, почти скриптовое использование синтаксиса C ++. Вот одно предложение, которое углубляется в это динамическое использование, где вы пишете сценарий с этими свойствами, не требуя повторной компиляции. (Это конкретное предложение основано на поведении типа QMetaObject, но есть и другие предложения с похожими соображениями об использовании):

http://www.codeproject.com/KB/cpp/dynamic_cpp.aspx

Если вы используете Google «Dynamic C ++» или «C ++ Scripting», у вас может появиться еще несколько идей. В некоторых из этих вещей есть злобные умные мысли.

1 голос
/ 13 июня 2014

Мне нравится ответ 6502. Он использует меньше памяти и работает быстрее, чем решение, которое я представлю.Только у меня будет немного синтаксического сахара.

Я хотел бы иметь возможность увидеть что-то вроде этого (с идиомой PIMPL):

class A {
private:
    class FImpl;
    FImpl* Impl;

public:
    A();
    ~A();

    Property<int> Count;
    Property<int> Count2;
    Property<UnicodeString> Str;
    Property<UnicodeString> Readonly;
};

Вот идет полный код (я уверен, чтоэто стандартное соответствие):

template <typename value_t>
class IProperty_Forward {
public:
    virtual ~IProperty_Forward() {}
    virtual const value_t& Read() = 0;
    virtual void Set(const value_t& value) = 0;
};

template <typename value_t, typename owner_t, typename getter_t, typename setter_t>
class TProperty_Forwarder: public IProperty_Forward<value_t>
{
private:
    owner_t* Owner;
    getter_t Getter;
    setter_t Setter;
public:
    TProperty_Forwarder(owner_t* owner, getter_t& getter, setter_t& setter)
    :Owner(owner), Getter(getter), Setter(setter)
    { }

    const value_t& Read()
        { return (Owner->*Getter)(); }

    void Set(const value_t& value)
        { (Owner->*Setter)(value); }
};

template <typename value_t>
class Property {
private:
    IProperty_Forward<value_t>* forward;
public:
    Property():forward(NULL) { }

    template <typename owner_t, typename getter_t, typename setter_t>
    Property(owner_t* owner, getter_t getter, setter_t setter)
        { Init(owner, getter, setter); }

    ~Property()
        { delete forward; }

    template <typename owner_t, typename getter_t, typename setter_t>
    void Init(owner_t* owner, getter_t getter, setter_t setter)
    {
        forward = new TProperty_Forwarder<value_t, owner_t, getter_t, setter_t>(owner, getter, setter);
    }

    Property& operator=(const value_t& value)
    {
        forward->Set(value);
        return *this;
    }

    const value_t* operator->()
    { return &forward->Read(); }

    const value_t& operator()()
        { return forward->Read(); }

    const value_t& operator()(const value_t& value)
    {
        forward->Set(value);
        return forward->Read();
    }

    operator const value_t&()
        { return forward->Read(); }
};    

И некоторые подробности реализации:

class A::FImpl {
    public:
        FImpl():FCount(0),FCount2(0),FReadonly("Hello") { }

        UnicodeString FReadonly;
        const UnicodeString& getReadonly()
            { return FReadonly; }
        void setReadonly(const UnicodeString& s)
            { }

        int FCount;
        int getCount()
            { return FCount; }
        void setCount(int s)
            { FCount = s; }

        int FCount2;
        int getCount2()
            { return FCount2; }
        void setCount2(int s)
            { FCount2 = s; }

        UnicodeString FStr;
        const UnicodeString& getStr()
            { return FStr; }
        void setStr(const UnicodeString& s)
            { FStr = s; }
};

A::A():Impl(new FImpl)
{
    Count.Init(Impl, &FImpl::getCount, &FImpl::setCount);
    Count2.Init(Impl, &FImpl::getCount2, &FImpl::setCount2);
    Str.Init(Impl, &FImpl::getStr, &FImpl::setStr);
    Readonly.Init(Impl, &FImpl::getReadonly, &FImpl::setReadonly);
}

A::~A()
{
    delete Impl;
}

Я использую C ++ Builder для всех, кто интересуется классом UnicodeString.Надеюсь, что это поможет другим для экспериментирования со стандартом, соответствующим свойствам c ++.Базовый механизм такой же, как 6502, с теми же ограничениями.

...