Как мне получить свойства в классе С++, как в классе С#.
Я не хочу иметь методы getter и setter.
Как мне получить свойства в классе С++, как в классе С#.
Я не хочу иметь методы getter и setter.
Вы можете использовать решение, подобное предложению Джона, но сохраняя обычную семантику С++, используя перегрузку оператора. Я немного изменил код Джона следующим образом (объяснения следуют за кодом):
#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=
, чтобы сохранить обычный синтаксис присваивания вместо синтаксиса, подобного функции. Мы используем оператор литья как "геттер". Нам нужна вторая версия operator=
, чтобы разрешить присвоение второго рода в main()
.
Теперь вы можете добавить в указатели функций конструктора Accessor, или лучше - функторы - вызвать как getters/seters каким-либо образом, кажется вам правильным. В следующем примере предполагается, что функция setter возвращает bool, чтобы передать согласие на установку нового значения, и геттер может просто изменить его на выходе:
#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 & позволяет пользователям обойти сеттер, так как он дает им доступ к частному значению. Один из способов решить эту проблему - реализовать все операторы, которые вы хотите предоставить своему Аксессуру. Например, в следующем коде я использовал оператор + =, и поскольку я удалил оператор переноса, возвращающий ссылку, мне пришлось реализовать 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;
}
Вам нужно будет реализовать все операторы, которые вы собираетесь использовать.
Для поведения такого типа я использую шаблонный метааксесуатор. Здесь очень упрощен для типов 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();
Компилятор обычно строит эти вызовы, если вы правильно настроите настройки и включили оптимизацию. Это не более узкое место в производительности, чем использование реальных геттеров и сеттеров, независимо от того, какие оптимизации происходят. Тривиально расширять это, чтобы автоматически поддерживать не-POD или перечисленные типы или разрешать регистрацию обратных вызовов для всякий раз, когда считываются или записываются данные.
Изменить. Если вы не хотите использовать круглые скобки, вы всегда можете определить operator=()
и неявный оператор литья. Здесь версия, которая делает именно это, а также добавляет базовую поддержку "обратного вызова":
Дальнейшее редактирование: Ладно, полностью пропустил, что кто-то уже сделал исправленную версию моего кода. Вздох.
Здесь реализация 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;
}
Есть два раздражения при таком подходе:
Если вам все равно, что ваш код на С++ не будет компилироваться ни с чем другим, кроме компилятора Microsoft Visual С++, вы можете использовать некоторые из нестандартных расширений компилятора.
Например, следующий код создаст свойство типа С#, называемое MyProperty
.
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 можно найти здесь.
Нет. С++ не поддерживает такие свойства, как С#. Если вы хотите, чтобы код работал на set/get, это должен быть метод.
Свойства не поддерживаются в С++, но вы можете их реализовать:
1) Используя шаблоны
2) С помощью расширения языка и написания препроцессора пользовательского кода
Любой подход будет непростым, но это можно сделать.
Вы можете предоставить методы 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.
Ближайшее совпадение свойств - объявить членов данных общедоступными.