Visual C++ 实现属性 Property

这是个很多人都写过的程序,想用 Visual C++ 或者标准 C++ 模仿 C# 的属性,我自己也写了一个。大体上是要实现如下的效果:

#include<iostream>

class foo
{
private:
    class PropertyDouble
    {
        double x;
    public:
        PropertyDouble() { x = 0; }

        operator double() // 查询,Get。
        {
            std::cout << "foo::PropertyDouble::operator double()/n";
            return x;
        }
        double operator =(double d) // 赋值,Set。也可以返回 void
        {
            std::cout << "double foo::PropertyDouble::operator =(double)/n";
            return x = d;
        }
    };

public:
    PropertyDouble wealth;
};

int main()
{
    foo bar;

    bar.wealth = bar.wealth + 100;
    std::cout << bar.wealth << std::endl;

    //bar.wealth += 100; // 未定义 +=

    std::cin.get();
}

在使用的时候,bar.wealth 根据环境解释为取值或者赋值。这要求对 Get 实现 operator double(),对 Set 实现 operator =(double x)。使用属性的代码最好像下面这样:

private double x = 0;

public double wealth
{
    get { return x; }
    set { x = value; }
}

当然,在 C++ 中上述语法较难实现(我不知道如何实现),我觉得下面的语法也可以接受:

private:
    double x;
   
    double getX() { return x; }
    double setX(double d) { x = d; }

public:
    Property<double, ...> wealth;

首先想到的是,定义一个基类模板,公开 operator T() 和 operator =(T& x),要实现属性时,从这个基类模板的一个实例继承,类似下面这样:

template<typename Type, typename Child>
class Property
{
    Type x;

protected:
    Property() : x(Type()) { }

    Type Get()
    {
        return x;
    }
    Type Set(Type const& y)
    {
        return x = y;
    }

public:
    operator Type()
    {
        std::cout << "Property::operator T()/n";
        return static_cast<Child*>(this)->Get();
    }
    Type operator =(Type const& y)
    {
        return static_cast<Child*>(this)->Set(y);
    }
};

struct foo
{
    class Prop1 : public Property<double, Prop1>
    {
        friend class Property<double, Prop1>;

        double Get() { std::cout << "foo::Get/n"; return Property<double, Prop1>::Get(); }
        double Set(double const& x) { std::cout << "foo::Set/n"; }
    } wealth;
};

这种做法行不通,子类不继承基类的赋值操作符。另外,把实际的数值保存在基类中也不合适,有些属性可能不对应一个字段。于是想到如下的方式,需要属性的类包含一个 Property 的实例:

template<typename TValue, typename TClass>
class Property

构造函数
Property(TClass* pObject, TValue const& (TClass::* pmGet)() const, TValue const& (TClass::* pmSet)(TValue const&))

实际的代码在下面列出。这个实现也有两点麻烦:
1. Property 被包含,因此在包含它的类之前构造,但是它的构造函数需要一个包含类的指针 TClass* pObject,这会引起一个警告
2. 必须按下列形式使用,要兼顾到成员定义和构造函数,比较麻烦,特别在 foo 有好几个构造函数时
class foo
{
    double m_d;
    double const& GetD() const { }
    double const& SetD(double const& x) { }
   
public:
    foo() : m_d(0), wealth(this, &foo::GetD, &foo::SetD) { }
    Property<double, foo> wealth;
};

下面的代码解决了第一点,使用一个偏移量代替 foo 指针;然而第二点在不增加函数的前提下,无法解决:

#include <iostream>
#include <stdexcept>
#include <string>

template<typename TValue, typename TClass>
class Property
{
private:
    typedef TValue const& (__thiscall TClass::* pGetT)() const;
    typedef TValue const& (__thiscall TClass::* pSetT)(TValue const& x);

    TClass* pHost;
    pGetT pGet;
    pSetT pSet;

public:
    Property(size_t offset, pGetT pmGet, pSetT pmSet)
    {
#if defined _WIN64
        pHost = (TClass*)((unsigned long long)this - offset);
#else
        pHost = (TClass*)((unsigned int)this - offset);
#endif

        pGet = pmGet;
        pSet = pmSet;
    }
    operator TValue() const
    {
        std::cout << "Property::operator T()/n";
        if (pGet)
            return (pHost->*pGet)();
        else
            throw std::logic_error("没有给出对象的 Get 方法");
    }
    TValue const& operator =(TValue const& x)
    {
        std::cout << "T Property::operator =(T&)/n";
        if (pSet)
            return (pHost->*pSet)(x);
        else
            throw std::logic_error("没有给出对象的 Set 方法");
    }
};

class foo
{
    double m_d;
    double const& GetD() const
    {
        std::cout << "foo::GetD/n";
        return m_d;
    }
    double const& SetD(double const& x)
    {
        std::cout << "foo::SetD/n";
        return m_d = x;
    }

    std::string m_s;
    std::string const& GetS() const
    {
        return m_s;
    }
    std::string const& SetS(std::string const& text)
    {
        return m_s = text;
    }

public:
    foo()
        : m_d(0), wealth(offsetof(foo, wealth), &foo::GetD, &foo::SetD)
        , m_s(""), title(offsetof(foo, title), &foo::GetS, &foo::SetS)
    { }

    Property<double, foo> wealth;
    Property<std::string, foo> title;
};

int main()
{
    foo bar;

    bar.wealth = bar.wealth + 10;
    std::cout << bar.wealth << std::endl;
    //std::cout << (bar.wealth += 5) << std::endl;

    //std::cout << bar.title << std::endl;
    // 不能工作。std::cout 没有为 std::string 重载成员 <<,std::string 重载了
    // 全局的 operator <<,这是八个模板函数。这导致打印 bar.title 的时候在
    // std::cout 的成员中找不到匹配,而全局的 operator << 是模板,模板不允许
    // 隐式转换。所以 bar.title 不合格。想打印 bar.title 就必须了解 std::cout
    // 的 operator << 都允许哪些类型。
}

如上所说,当 foo 有很多构造函数时,成员初始化列表会很烦。另外每个 Property 实例都保存一个 foo 的指针,如果一个 foo 有好几个属性,这样就是重复存储。

由于需要在 Property 中调用 foo 的成员函数,所以必须给 Property 传递 foo 的指针,这不能在实例化 Property 模板的时候完成,因为在写下下面语句的时候
Property<double, foo> wealth;
foo 还没有完全定义,此时既没有办法给模板传递 wealth 的偏移量,也不能传递 foo 实例的指针。

Visual C++ 有个属性很好的实现,所以我不想继续修改 Property 模板了,如果各位有更好的方法请留言。在 Visual C++ 中使用 __declspec(property) 实现属性:

// declspec_property.cpp

struct S
{
    int i;

    void putprop(int j) { i = j; }
    int getprop() { return i; }

    __declspec(property(get = getprop, put = putprop)) int the_prop;
};

int main()
{
    S s;
    s.the_prop = 5;
    s.the_prop += 5;
    return s.the_prop;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值