Доброе утро!
В данной статье я покажу, как в C++ можно реализовать properties, подобно тому, как это сделано в C# (но с чуть меньшим сахарком).
Для начала, поймём, что же это. Properties (они же свойства) — это такие поля, обращение к которым идёт через неявный вызов геттеров и сеттеров. То есть, эдакий синтаксический сахар. Мне он не нравится как минимум по причине неявного вызова функции за синтаксисом обращения к полю, но реализовать такую функциональность на C++ захотелось попробовать.
Теперь к реализации. Идея простая. Создадим класс, от которого все эти свойства будут наследоваться. В нём добавим виртуальные методы set и get, по-умолчанию определённые такими, какими их и привычно видеть, а конструкторы, операторы присваивания и операторы работы с потоками ввода-вывода определим через них. Получается следующая милота:
#include <iostream>
template <typename T>
struct Property {
protected:
T value;
public:
Property() = default;
Property(const T &value) : Property() {
set(value);
}
const T & operator=(const T &value) {
set(value);
return this->value;
}
operator T() const {
return get();
}
virtual void set(const T &value) {
this->value = value;
}
virtual T get() const {
return value;
}
};
template <typename T>
std::ostream& operator<<(std::ostream &os, const Property<T> &prop) {
os << static_cast<T>(prop);
return os;
}
template <typename T>
std::istream& operator>>(std::istream &is, Property<T> &prop) {
T val;
is >> val;
prop = val;
return is;
}
Теперь мы можем создавать внутренние классы, сразу же объявляя поля, и перегружая методы на своё усмотрение. В геттере и сеттере получаем доступ к this->value
, а в сеттере есть аргумент value
, почти как в родном шарпе. Ну и добавим макросов (куда ж без них?), чтобы жилось совсем красиво:
#define PROPERTY_S(name, type, setter) \
struct name##name : Property<type> { \
name##name() = default; \
name##name(type value) : name##name() { \
set(value); \
} \
void set(const type &value) setter \
} name
#define PROPERTY_SD(name, type, defval, setter) \
struct name##name : Property<type> { \
name##name() : Property(defval) {} \
name##name(type value) : name##name() { \
set(value); \
} \
void set(const type &value) setter \
} name
#define PROPERTY_G(name, type, getter) \
struct name##name : Property<type> { \
name##name() = default; \
name##name(type value) : name##name() { \
set(value); \
} \
int get() const getter \
} name
#define PROPERTY_GD(name, type, defval, getter) \
struct name##name : Property<type> { \
name##name() : Property(defval) {} \
name##name(type value) : name##name() { \
set(value); \
} \
int get() const getter \
} name
#define PROPERTY_D(name, type, defval, setter, getter) \
struct name##name : Property<type> { \
name##name() : Property(defval) {} \
name##name(type value) : name##name() { \
set(value); \
} \
void set(const type &value) setter \
int get() const getter \
} name
#define PROPERTY(name, type, setter, getter) \
struct name##name : Property<type> { \
name##name() = default; \
name##name(type value) : name##name() { \
set(value); \
} \
void set(const type &value) setter \
int get() const getter \
} name
Накидали макросов на все случаи жизни. Использование довольно просто: name
— итоговое имя свойства (лучше не дублировать), type
— тип хранимого значения, defval
— умолчательное значение, с которым оно будет сконструировано, а getter
и setter
— блоки кода, тела соответствующих функций.
Добавлю компилирующийся пример, чтобы лучше видеть, как это работает:
#include <iostream>
template <typename T>
struct Property {
protected:
T value;
public:
Property() = default;
Property(const T &value) : Property() {
set(value);
}
const T & operator=(const T &value) {
set(value);
return this->value;
}
operator T() const {
return get();
}
virtual void set(const T &value) {
this->value = value;
}
virtual T get() const {
return value;
}
};
template <typename T>
std::ostream& operator<<(std::ostream &os, const Property<T> &prop) {
os << static_cast<T>(prop);
return os;
}
template <typename T>
std::istream& operator>>(std::istream &is, Property<T> &prop) {
T val;
is >> val;
prop = val;
return is;
}
#define PROPERTY_S(name, type, setter) \
struct name##name : Property<type> { \
name##name() = default; \
name##name(type value) : name##name() { \
set(value); \
} \
void set(const type &value) setter \
} name
#define PROPERTY_SD(name, type, defval, setter) \
struct name##name : Property<type> { \
name##name() : Property(defval) {} \
name##name(type value) : name##name() { \
set(value); \
} \
void set(const type &value) setter \
} name
#define PROPERTY_G(name, type, getter) \
struct name##name : Property<type> { \
name##name() = default; \
name##name(type value) : name##name() { \
set(value); \
} \
int get() const getter \
} name
#define PROPERTY_GD(name, type, defval, getter) \
struct name##name : Property<type> { \
name##name() : Property(defval) {} \
name##name(type value) : name##name() { \
set(value); \
} \
int get() const getter \
} name
#define PROPERTY_D(name, type, defval, setter, getter) \
struct name##name : Property<type> { \
name##name() : Property(defval) {} \
name##name(type value) : name##name() { \
set(value); \
} \
void set(const type &value) setter \
int get() const getter \
} name
#define PROPERTY(name, type, setter, getter) \
struct name##name : Property<type> { \
name##name() = default; \
name##name(type value) : name##name() { \
set(value); \
} \
void set(const type &value) setter \
int get() const getter \
} name
// \\ // \\
//EXAMPLE//
// \\ // \\
class A {
public:
PROPERTY(val, int, { this->value = 2*value; }, { return 3*value; });
};
int main() {
A obj;
std::cin >> obj.val; // Enter 2 (stored 2*2=4)
std::cerr << obj.val << std::endl; // See 3*4=12
int a = obj.val; // Get 3*4=12
std::cerr << a << std::endl; // See 12
obj.val = a; // Store 2*12=24
std::cerr << obj.val; // See 3*24=72
return 0;
}
Прямо как в C#!
Вот мы и завезли properties в C++. Было бы интересно посмотреть на реализацию подобного механизма в какой-нибудь Java, если кто-то отважится попробовать.