原生c++属性的可视化可移植性

visual Portability of Native C++ properties

本文关键字:可视化 可移植性 属性 c++ 原生      更新时间:2023-10-16

在Visual Studio中,有__declspec(property)创建类似于c#的属性。Borland c++提供了具有完全相同功能的__property关键字。在c++ 0x中,提到了一个implicit关键字,可以扩展以实现相同的功能。但它并没有出现在规范中。

我正在寻找一种可移植的和相对干净的方法来声明语法上加糖的属性,它将在Windows, OSX和Linux的最新编译器中编译。我不关心编译器兼容性,只关心每个平台一个编译器。

我不寻找需要括号来获取或设置属性的属性的替代方法,例如分隔getter和setter的重载方法。

下面是在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;
}

这与您所要求的类似,并且(我希望)是标准的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 assignn";
    foo.x = foo2.x;
    std::cout << "calling a function accepting an int parametern";
    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*参数的函数。

Clang现在已经完全实现了Microsoft __declspec(property...),并且优化得很漂亮。所以你可以在所有平台的c++中使用属性,并在基于gcc或c99的代码中混合使用。

我已经使用它一年多了,等待它普遍出现已经五年多了。

它是用于抽象结构和重构代码的最强大的c++工具之一。我一直使用它来让我快速构建一个结构,然后在性能或重组需要时重构它。

它是无价的,我真的不明白为什么c++标准很久以前没有采用它。但话说回来,它们有那么多复杂和臃肿的boost方法来使用c++和模板。

Clang现在在每个平台上都是可移植的,拥有这个功能真是太棒了。

(免费或付费版本) Visual Studio中使用clang进行开发几乎是无缝的,并且您可以获得令人难以置信的调试开发工具集,相比之下,在其他工具集和平台上工作非常痛苦。

我现在在所有的c++开发中专门使用clang

参见:this cross-reference post

我在找一个便携式的相对干净的申报方法语法上粉饰了属性会在最新的编译器中编译吗

您正在描述"元对象"类型的功能,如编译时或运行时定义的属性,例如那些可能通过"Java bean"或"Java bean"实现的属性。. NET反射",或者使用高级脚本语言(如Python和Perl)的任何方法。

例如,你描述的(编译时和/或运行时属性)是通过QMetaObject在Qt (c++)库中实现的。您可以直接实例化它,将其作为类中的"成员"使用,或者从QObject派生以"自动"获得元对象行为(以及其他一些东西,如"cast"帮助和信号/槽交叉线程)。当然,这些都是跨平台的(例如,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

如果你搜索"动态c++"或"c++脚本",你可能会得到更多的想法。这些东西里有一些非常聪明的想法。

我喜欢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相同,具有相同的限制。