Подтвердить что ты не робот

Переносимость собственных свойств С++

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

Я ищу переносимый и относительно чистый метод объявления синтаксически sugared-свойств, которые будут компилироваться в последних компиляторах для 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;
}
4b9b3361

Ответ 1

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

#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*.

Ответ 2

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

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

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

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

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

Наконец, вы начинаете слегка касаться домена "Dynamic С++", что подразумевает более легкое, почти script -подобное использование синтаксиса С++. Здесь одно предложение, которое немного углубляется об этом динамическом использовании, где вы script с этими свойствами, не нуждаясь в повторной компиляции. (Это конкретное предложение основано на поведении типа QMetaObject, но есть и другие предложения с похожими мыслями об использовании):

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

Если вы используете Google Dynamic С++ или С++ Scripting, вы можете получить еще несколько идей. В некоторых вещах есть какие-то злые умные мысли.

Ответ 3

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

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

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

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

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

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

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

Ответ 4

Мне нравится ответ 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;
}

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