这是个很多人都写过的程序,想用 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;
}