开发者

Portability of Native C++ properties

In Visual Studio, there is __declspec(property) which creates properties similar to C#. Borland C++ offers the __property keyword with the exact same functionality. In the C++0x, there is mention of a implicit keyword that could be expanded to implement the same functionality. But it didn't make it into the spec.

I am looking for a portable and relatively clean method of declaring syntactically sugared properties that will compile in the latest compilers for Windows, OSX and Linux. I am not concerned with compiler compatibility, just one compiler per platform.

I am not looking for alternatives to properties that require parenthesis to get or set the property, such as overloaded methods separating the getters and setters.

Here is an ideal usage which compiles in 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();
 开发者_如何学Python   bool GetVisible();
    void SetVisible(bool);
}

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


This is something similar to what you are asking and is (I hope) standard 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;
}

As you can see from main the usage is transparent as long as you are assigning values of type T (here int) or implicitly convertible to T to properties and as long you are converting them back to T values on reading.

Behavior will be different however if you for example pass foo.x to a template function because the type of foo.x is not int but Property<Foo, int, ...> instead.

You can also have problems with non-template functions... calling a function accepting a T value will work fine, however a T& parameter is for example going to be a problem because basically the function is asking a variable to access directly using the address. For the same reason you cannot pass of course the address of a property to a function accepting a T* parameter.


Clang now has the Microsoft __declspec(property...) fully implemented and it optimizes beautifully. So you can use properties in your c++ across all platforms and intermix in gcc based or c99 code etc.

I have been using it for over a year, and waited for this to appear universally for more than five years.

It is one of the most powerful C++ tools for abstracting structure and refactoring code. I use it all the time to allow me to quickly build a structure and then refactor it later as performance or restructuring requires it.

It is invaluable and I really don't understand why the C++ standards have not adopted it long ago. But then again, they have so much of the complex and bloated boost way of using c++ and templates.

Clang is so portable across every platform now that having this feature is fantastic.

Development within (free or paid version of) Visual Studio using clang is almost seamless and you get the incredible debugging development toolset that just makes working on other toolsets and platforms painful by comparison.

I exclusively use clang now for all my c++ development.

See also: this cross-reference post


I am looking for a portable and relatively clean method of declaring syntactically sugared properties that will compile in the latest compilers for Windows, OSX and Linux.

You're describing "meta-object" type capabilities, like compile-time or run-time defined properties, such as those that may be otherwise implemented through "Java beans" or ".NET reflection", or any number of ways with high-level scripting languages, like Python and Perl.

For example, what you're describing (compile-time and/or run-time properties) is implemented in the Qt (C++) libraries through the QMetaObject. You can instantiate it directly, use it as a "member" in your classes, or derive from QObject to "automatically" get that meta-object behavior (and some other things, like "casting" helps, and signals/slots cross-threads). Of course, these are quite cross-platform (e.g., Win, Mac, Posix).

I'm not a big fan of the __declspec() usage, except for very platform-specific use, such as explicit exporting of types through a "Microsoft Extension DLL" (which I generally try to avoid if possible). I don't think there's any way to make such usage "cross-platform" (since that particular usage is specific to MS DLLs).

Similarly, it wouldn't be very difficult to write your own "MyMetaObject" type class that is essentially a "dictionary" or "hash" or "associative array", which your objects use, and which is populated dynamically at runtime, even with your internal types (such as MyColor, MyTime, MyFilePath, etc.) I've done that several times, and it need not be lots of work, and it can work quite elegantly. (The QMetaObject is typically quite a bit more powerful than these simple approaches, but it requires the "moc" compilation step, which is a very powerful step to generate fast lookup code for its properties, and to enable signals/slots).

Finally, you're starting to touch lightly into the "Dynamic C++" domain, which implies lighter, almost script-like usage of C++ syntax. Here's one proposal that goes into a bit of depth about this dynamic usage, where you script with these properties, not needing to re-compile. (This particular proposal happens to be based on the QMetaObject type behavior, but there are other proposals with similar usage thoughts):

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

If you google "Dynamic C++" or "C++ Scripting", you might get some more ideas. There's some wickedly clever thoughts in some of that stuff.


I like the answer of 6502. It uses both less memory and is faster than the solution i will present. Only mine will have a bit syntactic sugar.

I wanted to be able to wite something like this (with PIMPL idiom):

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

public:
    A();
    ~A();

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

Here comes the completet code (I am quite sure it is standard conformant):

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

And some implementation details:

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;
}

I am using C++ Builder for anyone who wonders about the UnicodeString class. Hope it helps others for experimentation of Standard conforming c++ Properties. The basic mechanism is the same as 6502, with the same limitations.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜