如何在C ++类中拥有属性,就像在C#类中一样。
我不想使用getter和setter方法。
答案 0 :(得分:12)
您可以使用类似于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=
以保留通常的赋值语法,而不是类似函数调用的语法。我们使用cast操作符作为“getter”。我们需要operator=
的第二个版本才能在main()
中分配第二种版本。
现在你可以添加到Accessor的构造函数指针,或者更好的 - 仿函数 - 以任何方式调用getter / setter似乎都是对的。下面的例子假设setter函数返回bool以传达设置新值的协议,并且getter可以在它出路时修改它:
#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;
}
但是,最后一行证明它有一个错误。演员返回T&amp;允许用户绕过setter,因为它允许用户访问私有值。解决此错误的一种方法是实现您希望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;
}
你必须实现你将要使用的所有操作符。
答案 1 :(得分: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=()
和隐式强制转换运算符。这是一个可以做到这一点的版本,同时还添加了基本的“发生的事情”回调支持:
进一步编辑:好的,完全错过了有人已经修改了我的代码版本。叹息。
答案 2 :(得分: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;
}
这种方法有两个烦恼:
答案 3 :(得分: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。
答案 4 :(得分:1)
你没有。 C ++不支持C#等属性。如果你想让代码在set / get上运行,它必须是一个方法。
答案 5 :(得分:1)
C ++不支持属性,但您可以实现它们:
1)使用模板
2)通过语言扩展和编写自定义代码预处理器
任何一种方法都不容易,但可以做到。
答案 6 :(得分: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;}
};
虽然这很接近,因为它需要对每个成员使用'()',但它不符合Microsoft Languages提供的属性的确切功能。
属性最接近的匹配是将数据成员声明为public。
答案 7 :(得分:0)
这里有一个粗略而简单的实现,它使用预处理器宏毫不费力地生成嵌套类,这些嵌套类为getter和setter的功能提供了优美而干净的语法,就像它们是实际变量一样。尽管使用了PROPERTIES
声明,但编译后的程序将具有名称property_ *的(子)类,但没有使用模板或函数指针(如果是加号的话),并且像Evan Teran的解决方案一样,您需要为该属性提供对构造函数中所属类的引用。
应该易于添加更多运算符(operator ++,operator + =)。
(我想对拥有类的引用是可以的,尽管是圆形的...?(x.property_age.x.property_age ...
))
#include <iostream>
#include <stdexcept>
#define PROPERTY(type, name, owner, get_exprs, set_exprs) \
friend class property_ ##name; \
class property_ ##name { \
owner & x; \
public: \
property_ ##name (owner & init): x(init) {} \
operator type () { \
get_exprs \
} \
type operator= (const type value) { \
set_exprs \
return value; \
} \
} name ;
int current_year = 2020;
class person {
int year_of_birth; // Integer is signed to demonstrate argument validation
public:
// Remember to add each property and *this for them
person(): year_of_birth(0), age(*this) {}
const int& born() { return year_of_birth; }
// Remember the semicolons
PROPERTY(int, age, person,
/*get:*/ return current_year - x.year_of_birth; ,
/*set:*/ if (value < 0) throw std::invalid_argument("person::age : age cannot be negative");
x.year_of_birth = current_year - value; )
};
int main() {
person alice, bob;
alice.age = bob.age = 28;
alice.age = alice.age + 5;
//alice.age = -7; //throws
// Apparently the compiler does nice implicit conversion from
// (the macro-generated class) 'property_age' to 'int'
std::cout << "Alice: born: " << alice.born() << ", age: " << alice.age << '\n'
<< "Bob: born: " << bob.born() << ", age: " << bob.age << '\n'
<< "The mean of their ages is: " << (alice.age + bob.age) / 2.0 << '\n';
return 0;
}