【问题标题】:Portability of Native C++ properties本机 C++ 属性的可移植性
【发布时间】:2011-08-11 23:07:36
【问题描述】:

在 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++0x 中没有implicit 关键字。我无法访问您发布的链接(它要求输入用户名/密码)。
  • 我更新了链接。看起来它从未进入 c++0x 规范。
  • 我的 0.02 美元很明显,如果您的目标是便携性,请远离这些扩展。期间。
  • 在“熟悉该语言的程序员应该能够理解我的代码”的意义上,可移植性如何?这是一种非常重要的可移植性,你丢弃它绝对没有任何好处。为什么?
  • 更多的是我能做什么,而不是我为什么不应该做。我有一长串不应该这样做的理由,但我想这样做。 C++ 已经是一门杂乱无章的语言,混合了新旧实现,具体取决于供应商,例如 for-each 实现。

标签: c++ visual-c++ gcc properties c++11


【解决方案1】:

我正在寻找一个便携式和 比较干净的声明方法 语法糖化的属性 将在最新的编译器中编译 适用于 Windows、OSX 和 Linux。

您正在描述“元对象”类型的功能,例如编译时或运行时定义的属性,例如可以通过“Java bean”或“.NET 反射”或任意数量的其他方式实现的属性使用高级脚本语言的方法,例如 Python 和 Perl。

例如,您所描述的(编译时和/或运行时属性)是通过QMetaObject 在 Qt (C++) 库中实现的。您可以直接实例化它,将其用作类中的“成员”,或从QObject 派生以“自动”获取元对象行为(以及其他一些东西,如“强制转换”帮助和信号/插槽交叉-线程)。当然,这些都是跨平台的(例如,Win、Mac、Posix)。

我不是__declspec() 用法的忠实拥护者,除了非常特定于平台的使用,例如通过“Microsoft Extension DLL”显式导出类型(如果可能,我通常会尽量避免)。我不认为有任何方法可以使这种用法“跨平台”(因为这种特定用法特定于 MS DLL)。

同样,编写自己的“MyMetaObject”类型类也不是很困难,它本质上是一个“字典”或“散列”或“关联数组”,您的对象使用这些类,并且动态填充在运行时,即使使用您的内部类型(例如MyColorMyTimeMyFilePath 等),我已经多次这样做了,它不需要做很多工作,它可以非常优雅地工作。 (QMetaObject 通常比这些简单的方法更强大,但它需要“moc”编译步骤,这是一个非常强大的步骤,可以为其属性生成快速查找代码,并启用信号/插槽)。

最后,您开始轻松接触“动态 C++”领域,这意味着 C++ 语法的使用更轻松,几乎类似于脚本。这是一个深入探讨这种动态用法的建议,您可以在其中使用这些属性编写脚本,而无需重新编译。 (这个特定的提案恰好是基于QMetaObject类型的行为,但也有其他的提案有类似的使用思路):

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

如果您在 Google 上搜索“动态 C++”或“C++ 脚本”,您可能会得到更多想法。在其中的一些东西里有一些非常聪明的想法。

【讨论】:

    【解决方案2】:

    这与您所要求的类似,并且是(我希望)标准 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&lt;Foo, int, ...&gt;

    您也可能在使用非模板函数时遇到问题...调用接受 T 值的函数可以正常工作,但是例如 T&amp; 参数会成为问题,因为基本上该函数正在询问使用地址直接访问的变量。出于同样的原因,您当然不能将属性的地址传递给接受T* 参数的函数。

    【讨论】:

    • 类似于a simple meta-accessor;唯一的区别是选择重载 operator() 而不是 operator=operator T,以及(某种)区分 POD 和非 POD 类型。
    • 最大的缺点是每个属性消耗一个额外的指针,但为了方便起见,它肯定是可以接受的。假设内联优化,它的开销很小。这也比在每个编译器中使用单独的专有方法定义要干净得多,而且我可以使用私有 getter 和 setter 来保持 IntelliSense 清洁。这使得将依赖于结构的代码转换为事件驱动的类变得非常容易。谢谢!
    • 昨晚我刚刚尝试了这个Property 实现,并立即激动不已(希望有类似 C# 的属性)。然后我尝试将该属性传递给非模板函数参数,并点击您在答案末尾提到的“不是int,而是Property&lt;Foo, int, ...&gt;”症结。因此,对于其他有兴趣尝试此操作的人,请记住,这些属性不能像它们的基础类型一样被传递。它们只不过是函数调用的语法糖。不多也不少。
    • @BretKuhns:我添加了关于参考参数的注释。对于T 类型的值,我预计不会出现问题...
    【解决方案3】:

    我喜欢 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;
    }
    

    我正在为任何想了解 UnicodeString 类的人使用 C++ Builder。 希望它可以帮助其他人试验符合标准的 c++ 属性。 基本机制与 6502 相同,但限制相同。

    【讨论】:

      【解决方案4】:

      Clang 现在已经完全实现了 Microsoft __declspec(property...),并且它进行了精美的优化。 因此,您可以在所有平台的 c++ 中使用 properties,并在基于 gcc 或 c99 代码等中混合使用。

      我用了一年多了,等了五年多了。

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

      它非常宝贵,我真的不明白为什么 C++ 标准很久以前没有采用它。但话说回来,他们有太多复杂而臃肿的 boost 方式来使用 c++ 和模板。

      现在,Clang 在每个平台上都非常便携,拥有这个功能真是太棒了。

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

      我现在专门使用 clang 进行所有 c++ 开发。

      另请参阅:this cross-reference post

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-01-12
        • 2010-10-29
        • 2023-03-05
        • 1970-01-01
        • 1970-01-01
        • 2013-05-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多