代码改变世界

在C++中用C#风格设置类的属性值

2011-07-04 22:31  menggucaoyuan  阅读(625)  评论(0编辑  收藏
在C#中,程序员可以写出如下风格的代码设置属性值:
private int _x;
public int x
{
    get
    {
        return _x;
    }
    set
    {
        _x = value;
    }
}
然后可以对_x值进行赋值或者取值:
someObject.x = 50;

如果C++也能写出如此风格的代码,则其应有如下形式:
private:
     int x;
public: 
     int getX()
     {
          return x;
      }
       void setX(int value)
       {
           x = value;
       }
调用这些函数时,有如下调用法:
someObject.setX(50); 
int x = someObject.getX();
好了,上面对两种风格进行了简略的叙述,现有问题:如果有的c++程序员是C#出身,对C++这种设置不顺眼,如何在C++写出类似于C#风格的代码?在 别的平台上,这个问题可能是无法解决掉的,但是C#是微软的,如果写的C++程序也是运行在微软的平台上,那么办法总是有的。

微软VC6.0及其以后的C++编译器都定义了如下的声明方法:
__declspec( property (put=setFunction, get=getFunction) ) data-type property-name;
然后再借助于宏,于是就一切皆有可能了^_^。作者另外还补充声明,如果作为非VC++用户,在你的平台上实现上面这行代码也是可能的,但是实现起来非常 麻烦,如果想实现__declspec( property (put, set),那就看你的功力了。如果你不熟悉上面这行代码的意义,我希望你了解后再看下面的代码。

如果你真的很懒,我可以给出一个简单的例子,说明这个修饰符的作用:
#include <iostream>
using namespace std;

class CTest
{
public:
    __declspec( property( get = GetX, put = PutX ) ) int x;
    __declspec( property( get = GetY, put = PutY ) ) int y[];
    int GetX(void) { return m_nX; }
    void PutX( int x ) { m_nX = x; }
    int GetY( int n )  { return m_nY[n]; }
    void PutY( int n, int y ) { m_nY[n] = y; }
private:
    int m_nX;
    int m_nY[100];
};

int main(int argc, char* argv[])
{
    CTest test;
    test.x = 3;
    test.y[3] = 4;
   
    std::cout << test.x <<std::endl;
    std::cout << test.y[3] <<std::endl;
    return 0;
}
从上面可以看出修饰符__declspec( property( set, get ) )所修饰的变量实际上是一个代理。

废话少说,看作者如何借助于宏和__declspec( property (put, get) )实现C#风格的Getter和Setter。
    #define PROPERTY(t,n)  __declspec( property ( put = property__set_##n, get = property__get_##n ) ) t n;/
        typedef t property__tmp_type_##n
    #define READONLY_PROPERTY(t,n) __declspec( property (get = property__get_##n) ) t n;/
        typedef t property__tmp_type_##n
    #define WRITEONLY_PROPERTY(t,n) __declspec( property (put = property__set_##n) ) t n;/
        typedef t property__tmp_type_##n
    #define GET(n) property__tmp_type_##n property__get_##n()
    #define SET(n) void property__set_##n(const property__tmp_type_##n& value)
如果你读过《深入浅出MFC》或者熟悉MFC的异常机制,就可以很容易的读懂以上代码,当然前提还得是你了解 __declspec( property (put, get) )的用法。如果你没有读过,我可以补充一点:这里面的符号“ ##”是连字符,即在预编译时把后面的子字符串和其前面的子字符串作为一个整体的字符串处理。 

有了上面的宏,你就可以在C++的一个类中通过声明一行代码就可以实现如下C#风格的Getter和Setter了:
    private:
        int _x;
    public:
        PROPERTY(int, x);
        GET(x)
        {
            return _x;
        } 
        SET(x)
        {
            _x = value;
        }
然后,你老人家就可以写出如下风格的代码了:
    someObject.x = 50;
    int x = someObject.x;

现在我把作者的代码进行宏展开:
    private:
        int _x;
    public:
        __declspec( property( put = property__set_x, get = property__get_x  ) ) int x;
        typedef int property__tmp_type_x;  // 这两行对应于 PROPERTY(int, x);
        property__tmp_type_x property__get_x() //这行对应于GET(n)
        {
            return _x;
        }
        void property__set__x(const property__tmp_type__x& value) //这行对应于SET(n)
        {
            _x = value;
        }
这里需要注意的是,宏GET(n)和SET(n)二者中的n其实只是在函数名中起作用,在"函数体"中没有没有使用。
如果,再把typedef去掉,则代码如下:
    private:
        int _x;
    public:
        __declspec( property( put = property__set_x, get = property__get_x  ) ) int x;
        int property__get_x()
        {
            return _x;
        }
        void property__set__x(const int& value)
        {
            _x = value;
        }


如果你想定义一个单向地设置属性值,则可以用READONLY_PROPERTY或者WRITEONLY_PROPERTY定义一个Read-Only或者Write-Only型的代理变量,相关的代码如下:
    private:
        int _x;
    public:
        READONLY_PROPERTY( int, length );
        GET(length)
        {
            return strlen(_x);
        }
 或:
    private:
        int _x;
    public:
        WRITEONLY_PROPERTY( int, size );
        SET( size )
        {
            _size = value;
        }
最后,作者给出了一个可运行的代码示例:
// properties.h

#ifndef _PROPERTIES_H
#define _PROPERTIES_H

#define PROPERTY(t,n)  __declspec( property ( put = property__set_##n, get = property__get_##n ) ) t n;/
    typedef t property__tmp_type_##n
#define READONLY_PROPERTY(t,n) __declspec( property (get = property__get_##n) ) t n;/
    typedef t property__tmp_type_##n
#define WRITEONLY_PROPERTY(t,n) __declspec( property (put = property__set_##n) ) t n;/
    typedef t property__tmp_type_##n

#define GET(n) property__tmp_type_##n property__get_##n()
#define SET(n) void property__set_##n(const property__tmp_type_##n& value)

#endif /* _PROPERTIES_H */

// main.cpp
#include <iostream>
#include <math.h>
#include "properties.h"
class Vector2
{
public:
    float x;
    float y;

    READONLY_PROPERTY(float, Length);
    GET(Length)
    {
        return sqrt((x*x + y*y));
    }
};

int main()
{
    Vector2 vec;
    vec.x = 1;
    vec.y = 1;
    std::cout << "Length of vector(" << vec.x << ", " << vec.y << ") = ";
    std::cout << vec.Length << "/n"; // <--- property, not a function call

    return 0;


经我测试,上述代码是可以运行的。另外,需要补充一下,经我观察,它定义的函数声明宏最后没有给出分号,如下:
#define PROPERTY(t,n)  __declspec( property ( put = property__set_##n, get = property__get_##n ) ) t n;/
    typedef t property__tmp_type_##n
最后的标识符"property__tmp_type_##n"后就没有分号,但是其在后面的实现代码中声明的语句后面就可以加一个分号,如"PROPERTY(int, nX);",  这样使得类里面的代码看起来自然一点。

本文原网址如下:
http://www.codeproject.com/KB/cpp/properties.aspx