2010-04-01 87 views
11

如何在C++类中拥有属性,就像在C#类中一样。拥有C++类中的公共属性

我不想有getter和setter方法。

+2

看看这里的类似问题:http://stackoverflow.com/questions/2017623/forward-unbreakable-accessor-class-templates-c。我们利用C++特性甩掉了一些实现属性的方法。 – 2010-04-01 11:30:07

+0

平台特定的或平台无关的C++?在Windows中,VC++编译器具有支持此功能的非标准扩展。请参阅下面的答案。 – 2010-04-01 20:12:21

回答

11

您可以使用类似于Jon所建议的解决方案,但使用运算符重载保留普通的C++语义。我稍微修改Jon的代码如下(解释遵循的代码):

#include <iostream> 

template<typename T> 
class Accessor { 
public: 
    explicit Accessor(const T& data) : value(data) {} 

    Accessor& operator=(const T& data) { value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { this->value = other.value; return *this; } 
    operator T() const { return value; } 
    operator T&() { return value; } 

private: 
    Accessor(const Accessor&); 


    T value; 

}; 

struct Point { 
    Point(int a = 0, int b = 0) : x(a), y(b) {} 
    Accessor<int> x; 
    Accessor<int> y; 
}; 

int main() { 
    Point p; 
    p.x = 10; 
    p.y = 20; 
    p.x++; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

我们超载operator=保留了通常的赋值语法,而不是一个函数调用的语法。我们使用演员操作符作为“getter”。我们需要operator=的第二个版本来允许在main()中分配第二种类型。

现在,您可以添加到Accessor的构造函数指针或更好的函数 - 以任何方式调用getter/setters似乎是正确的。下面的示例假设setter函数返回布尔传达协议设置新值,吸气剂可以只修改它的出路:

#include <iostream> 
#include <functional> 
#include <cmath> 

template<typename T> 
class MySetter { 
public: 
    bool operator()(const T& data) 
    { 
     return (data <= 20 ? true : false); 
    } 
}; 

template<typename T> 
class MyGetter { 
public: 
    T operator()(const T& data) 
    { 
     return round(data, 2); 
    } 

private: 
    double cint(double x) { 
     double dummy; 
     if (modf(x,&dummy) >= 0.5) { 
      return (x >= 0 ? ceil(x) : floor(x)); 
     } else { 
      return (x < 0 ? ceil(x) : floor(x)); 
     } 
    } 

    double round(double r, int places) { 
     double off = pow(10.0L, places); 
     return cint(r*off)/off; 
    } 
}; 

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>> 
class Accessor { 
public: 
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {} 

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; } 
    operator T() const { value = getter(value); return value;} 
    operator T&() { value = getter(value); return value; } 

private: 
    Accessor(const Accessor&); 

    T value; 

    G getter; 
    S setter; 

}; 

struct Point { 
    Point(double a = 0, double b = 0) : x(a), y(b) {} 
    Accessor<double> x; 
    Accessor<double> y; 
}; 

int main() { 
    Point p; 
    p.x = 10.712; 
    p.y = 20.3456; 
    p.x+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15.6426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 25.85426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 19.8425; 
    p.y+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

然而,随着最后一行表明,它有一个bug。返回T &的演员操作员允许用户绕过设置者,因为它允许他们访问私有值。解决这个错误的一种方法是实现你希望Accessor提供的所有操作符。例如,在下面的代码我使用了+ =运营商,因为我脱离的铸造操作返回参考我不得不实施operator+=

#include <iostream> 
#include <functional> 
#include <cmath> 

template<typename T> 
class MySetter { 
public: 
    bool operator()(const T& data) const { 
     return (data <= 20 ? true : false); 
    } 
}; 

template<typename T> 
class MyGetter { 
public: 
    T operator() (const T& data) const { 
     return round(data, 2); 
    } 

private: 
    double cint(double x) const { 
     double dummy; 
     if (modf(x,&dummy) >= 0.5) { 
      return (x >= 0 ? ceil(x) : floor(x)); 
     } else { 
      return (x < 0 ? ceil(x) : floor(x)); 
     } 
    } 

    double round(double r, int places) const { 
     double off = pow(10.0L, places); 
     return cint(r*off)/off; 
    } 
}; 

template<typename T, typename G = MyGetter<T>, typename S = MySetter<T>> 
class Accessor { 
private: 
public: 
    explicit Accessor(const T& data, const G& g = G(), const S& s = S()) : value(data), getter(g), setter(s) {} 

    Accessor& operator=(const T& data) { if (setter(data)) value = data; return *this; } 
    Accessor& operator=(const Accessor& other) { if (setter(other.value)) this->value = other.value; return *this; } 
    operator T() const { return getter(value);} 

    Accessor& operator+=(const T& data) { if (setter(value+data)) value += data; return *this; } 

private: 
    Accessor(const Accessor&); 

    T value; 

    G getter; 
    S setter; 

}; 

struct Point { 
    Point(double a = 0, double b = 0) : x(a), y(b) {} 
    Accessor<double> x; 
    Accessor<double> y; 
}; 

int main() { 
    Point p; 
    p.x = 10.712; 
    p.y = 20.3456; 
    p.x+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 15.6426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 25.85426; 
    std::cout << p.x << "," << p.y << std::endl; 

    p.x = p.y = 19.8425; 
    p.y+=1; 
    std::cout << p.x << "," << p.y << std::endl; 

    return 0; 
} 

你必须实现所有你要的运营商使用。

+0

+ 1击败了我,但我不喜欢这种提供大量运算符重载的想法。另一种处理外部失效值的方法是提供一个不同的“被写入”回调函数,当下一次读取值时会调用它。 – 2010-04-02 00:08:25

+0

考虑到你只在模板类中定义了所有这些运算符,我不认为这太可怕了。在我看来,STL或boost代码与此相比(以及一般来说太:))是可怕的。 但也有其他选择。必须考虑自己的情况并据此作出选择。 :) – conio 2010-04-04 00:52:50

1

你不知道。 C++不支持像C#那样的属性。如果你想让代码在set/get上运行,它必须是一个方法。

+0

属性真的可以在C#中运行方法。编译器将它从你身上隐藏起来。 – 2010-04-01 19:46:31

7

对于这样的行为,我使用模板化的元访问器。这是一个高度简化的一个POD类型:

template<class T> 
struct accessor { 

    explicit accessor(const T& data) : value(data) {} 
    T operator()() const { return value; } 
    T& operator()() { return value; } 
    void operator()(const T& data) { value = data; } 

private: 

    accessor(const accessor&); 
    accessor& operator=(const accessor&); 
    T value; 

}; 

典型用法是这样的:

struct point { 
    point(int a = 0, int b = 0) : x(a), y(b) {} 
    accessor<int> x; 
    accessor<int> y; 
}; 

point p; 
p.x(10); 
p.y(20); 
p.x()++; 
std::cout << p.x(); 

编译器,如果你设置的东西,并有权利优化开启通常内联这些调用。与使用实际的getter和setter不同,它不再是性能瓶颈,无论发生什么优化。将它扩展为自动支持非POD或枚举类型或允许在读取或写入数据时允许注册回调是微不足道的。

编辑:如果您不想使用括号,则可以始终定义operator=()和隐式转换运算符。这里有一个版本可以做到这一点,同时也添加了基本的“东西发生”回调支持:

更多编辑:好的,完全错过了某人已经做了我的代码的修订版本。叹。

+0

当你可以定义operator =时,为什么使用笨重的函子风格? – 2010-04-01 23:24:16

+0

@Ben:为完整性添加差异。它忽略了C#中的那些垃圾。 – 2010-04-01 23:59:45

1

属性是不是在C++的支持,但你可以实现它们:
1)通过使用模板
2)通过使语言的扩展和编写自定义代码预处理器

两种方法都不会是一帆风顺的,但这是可以完成的。

+0

C++支持重载operator =,所以不需要扩展。 – 2010-04-01 23:21:33

+0

@ Ben Voigt这取决于你想要实现什么,以及你想要的属性数量。使用大量的代码,引入一个或两个关键字并编写代码预处理器将是更好的主意 - 它将使代码更具可读性。 – SigTerm 2010-04-02 15:22:20

3

这是我一段时间后做的一个PoC实现,除了需要在构造函数中设置一些东西以使其良好且流畅地工作以外,其效果很好。

http://www.codef00.com/code/Property.h

这里的用法示例:

#include <iostream> 
#include "Property.h" 


class TestClass { 
public: 
    // make sure to initialize the properties with pointers to the object 
    // which owns the property 
    TestClass() : m_Prop1(0), m_Prop3(0.5), prop1(this), prop2(this), prop3(this) { 
    } 

private: 
    int getProp1() const { 
     return m_Prop1; 
    } 

    void setProp1(int value) { 
     m_Prop1 = value; 
    } 

    int getProp2() const { 
     return 1234; 
    } 

    void setProp3(double value) { 
     m_Prop3 = value; 
    } 

    int m_Prop1; 
    double m_Prop3; 

public: 
    PropertyRW<int, TestClass, &TestClass::getProp1, &TestClass::setProp1> prop1; 
    PropertyRO<int, TestClass, &TestClass::getProp2> prop2; 
    PropertyWO<double, TestClass, &TestClass::setProp3> prop3; 
}; 

和这个类的一些使用...

int main() { 
    unsigned int a; 
    TestClass t; 
    t.prop1 = 10; 
    a = t.prop1; 
    t.prop3 = 5; 
    a = t.prop2; 
    std::cout << a << std::endl; 
    return 0; 
} 

有两个烦恼这种方法:

  1. 你需要克属性a 指向其拥有的类。
  2. 的语法来声明一个属性是 有点冗长,但我敢打赌,我可以清理 说了一下一些宏
+0

有点尴尬,是的,但使用'this'注册访问器的想法很好,因为它很容易让属性根据实例和类型信息玩弄技巧。 – 2010-04-02 00:20:10

1

您可以提供具有类似名称的数据成员get和set方法:

class Example 
{ 
    private: 
    unsigned int x_; 
    double d_; 
    std::string s_s; 
    public: 
    unsigned int x(void) const 
    { return x_;} 

    void x(unsigned int new_value) 
    { x_ = new_value;} 

    double d(void) const 
    { return d_;} 
    void d(double new_value) 
    { d_ = new_value;} 

    const std::string& s(void) const 
    { return s_;} 
    void s(const std::string& new_value) 
    { s_ = new_value;} 
}; 

虽然这种接近,因为它需要使用“()”的每个成员,它不符合性能,微软的语言提供具体的功能。

最接近的属性匹配是声明数据成员为公共。

+0

错误的是,通过重载operator =,你可以得到确切的语法(对于用户)和为你提供属性的任何语言的能力。 – 2010-04-01 23:23:02

3

如果您不关心您的C++代码不能用Microsoft Visual C++编译器以外的任何其他编译器编译,那么您可以使用某些编译器的非标准扩展。

例如,下面的代码将创建一个名为MyProperty的C#类属性。

struct MyType 
{ 
    // This function pair may be private (for clean encapsulation) 
    int get_number() const { return m_number; } 
    void set_number(int number) { m_number = number; } 

    __declspec(property(get=get_number, put=set_number)) int MyProperty; 
private: 
    int m_number: 
} 

int main() 
{ 
    MyType m; 
    m.MyProperty = 100; 
    return m.MyProperty; 
} 

有关此特定于Microsoft的语言扩展的更多信息,请访问here

相关问题