Как у меня есть свойства в классе C++, как Вы имеете в классе C#.
Я не хочу иметь методы получателя и методы установщика.
Вы можете использовать решение, подобное тому, что предложил Jon, сохраняя при этом обычную семантику C++ с помощью перегрузки операторов. Я немного изменил код Джона следующим образом (пояснения следуют за кодом):
#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 в качестве "геттера". Нам нужна вторая версия operator=
, чтобы разрешить присваивание второго рода в main()
.
Теперь вы можете добавить к конструктору Accessor функции-указатели, или лучше - функторы - для вызова в качестве геттеров/сеттеров любым способом, который кажется вам правильным. Следующий пример предполагает, что функция-сеттер возвращает 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;
}
Однако, как показывает последняя строка, здесь есть ошибка. Оператор cast, возвращающий T&, позволяет пользователям обойти сеттер, поскольку он дает им доступ к частному значению. Один из способов решения этой проблемы - реализовать все операторы, которые вы хотите, чтобы предоставлял ваш Accessor. Например, в следующем коде я использовал оператор +=, и поскольку я удалил оператор cast, возвращающий ссылку, мне пришлось реализовать 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 = ()
и неявный оператор приведения. Вот версия, которая делает именно это, а также добавляет базовую поддержку обратного вызова «что-то произошло»:
Дальнейшее редактирование : Хорошо, я совершенно пропустил, что кто-то уже сделал исправленную версию моего кода. Вздох.
Свойства не поддерживаются в C++, но вы можете реализовать их:
1) Используя шаблоны
2) С помощью расширения языка и написания препроцессора кода
Любой из этих подходов не будет простым, но это можно сделать.
Если вас не волнует, что ваш код на C++ не будет компилироваться ничем, кроме компилятора Microsoft Visual C++, то вы можете использовать некоторые нестандартные расширения компилятора.
Например, следующий код создаст C#-подобное свойство под названием 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 расширении языка можно найти здесь.
Вы можете предоставить методы получения и установки с похожими именами. к элементам данных:
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.
Наиболее близким подходом к свойствам является объявление элементов данных общедоступными.
Вот реализация 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;
}
У этого подхода есть два неудобства:
Вам не хватает. C ++ не поддерживает такие свойства, как C #. Если вы хотите, чтобы код запускался на set / get, это должен быть метод.