Всем привет! При реализации проектов разработчикам часто нужны классы, которые содержат только поля и не имеют никаких функций. Такие классы полезны для сохранения нужной информации с последующем их манипуляциями.
Первый подход в реализации таких классов основан на использовании С структур. Как недостаток этого подхода, это то что все поля доступные на запись и чтения, что не всегда хорошо.
Второй подход основан на скрытии всех полей и предоставлении для полей геттеров и сеттеров. Этот подход ярко используется в языке Java. В качестве преимущества можно выделить то, что мы можем управлять доступом к полям. К недостаткам можно отнести, то что класс стает большим, а геттеры и сеттеры не несут логической нагрузки.
В случае если поле типа класса, иногда нужно устанавливать объект по значению, по lvalue-ссылке, по rvalue-ссылке. А также с применением модификаторов const/volatile.
Много языков поддерживают свойства как языковую фичу. Написание кода становиться чище и надежнее. Чтобы упростить написание геттеров и сеттеров можно использовать макросы для генерации кода.
Но использование макросов опасно. Мы можем не верно указать тип (type) или переменную не того типа (name). В лучшем случае получим ошибку времени исполнении при использовании геттеров и сеттеров. В худшем случае ошибка останется.
Хотелось чтобы мы могли генерировать геттеры и сеттеры, но при этом могли проверить корректность типа (type), корректность типа переменой (name) и чтобы типы были равны. И это можно сделать начиная с С++11 используя type_traits стандартной библиотеки.
Используя данный подход можно реализовать геттеры и сеттеры для всех типов полей класса.
Все макросы для геттеров и сеттеров реализовал в виде header-only библиотеки. Подключив только один заголовочный файл можно легко реализовать дата класс со всеми необходимыми геттерами и сеттерами.
Исходный код библиотечки с открытым кодом можно посмотреть вот по этой ссылке.
Первый подход в реализации таких классов основан на использовании С структур. Как недостаток этого подхода, это то что все поля доступные на запись и чтения, что не всегда хорошо.
struct person {
int id;
human type;
std::string name;
std::string address[5];
bool merried;
};
Второй подход основан на скрытии всех полей и предоставлении для полей геттеров и сеттеров. Этот подход ярко используется в языке Java. В качестве преимущества можно выделить то, что мы можем управлять доступом к полям. К недостаткам можно отнести, то что класс стает большим, а геттеры и сеттеры не несут логической нагрузки.
class person {
public:
void set_id(int id) {
this->id = id;
}
int get_id() const {
return id;
}
void set_merried(bool merried) {
this->merried = merried;
}
bool is_merried() const {
return merried;
}
void set_type(human type) {
this->type = type;
}
human get_type() const {
return type;
}
void set_name(const std::string& name) {
this->name = name;
}
const std::string& get_name() const {
return name;
}
private:
int id;
human type;
std::string name;
std::string address[5];
bool merried;
};
В случае если поле типа класса, иногда нужно устанавливать объект по значению, по lvalue-ссылке, по rvalue-ссылке. А также с применением модификаторов const/volatile.
class person {
public:
void set_name(const std::string& name) {
this->name = name;
}
void set_name(std::string&& name) {
this->name = std::move(name);
}
const std::string& get_name() const {
return name;
}
private:
int id;
human type;
std::string name;
std::string address[5];
bool merried;
};
Много языков поддерживают свойства как языковую фичу. Написание кода становиться чище и надежнее. Чтобы упростить написание геттеров и сеттеров можно использовать макросы для генерации кода.
#define SETTER_PRIM(type, name) \
void set_##name(type value) { \
this->name = value; \
}
#define GETTER_PRIM(type, name) \
type get_##name() const { \
return name; \
}
Но использование макросов опасно. Мы можем не верно указать тип (type) или переменную не того типа (name). В лучшем случае получим ошибку времени исполнении при использовании геттеров и сеттеров. В худшем случае ошибка останется.
Хотелось чтобы мы могли генерировать геттеры и сеттеры, но при этом могли проверить корректность типа (type), корректность типа переменой (name) и чтобы типы были равны. И это можно сделать начиная с С++11 используя type_traits стандартной библиотеки.
#define SETTER_PRIM(type, name) \
void set_##name(type value) { \
using T1 = type; \
using T2 = decltype(name); \
static_assert(std::is_fundamental<T1>::value, \
"only primitive types"); \
static_assert(std::is_fundamental<T2>::value, \
"variable must be primitive"); \
static_assert(std::is_same<T1, T2>::value, \
"both types must be same"); \
this->name = value; \
}
#define GETTER_PRIM(type, name) \
type get_##name() const { \
using T1 = type; \
using T2 = decltype(name); \
static_assert(std::is_fundamental<T1>::value, \
"only primitive types"); \
static_assert(std::is_fundamental<T2>::value, \
"variable must be primitive"); \
static_assert(std::is_same<T1, T2>::value, \
"both types must be same"); \
return name; \
}
Используя данный подход можно реализовать геттеры и сеттеры для всех типов полей класса.
- примитивных типов
- объектных типов
- перечисления
- массива
- указателей
- ссылок
Все макросы для геттеров и сеттеров реализовал в виде header-only библиотеки. Подключив только один заголовочный файл можно легко реализовать дата класс со всеми необходимыми геттерами и сеттерами.
#include "property.hpp"
class person {
public:
person() = default;
~person() = default;
SETTER_PRIM(int, id);
SETTER_FLAG(bool, merried);
SETTER_ENUM(human, type);
SETTER_PTR(int, next);
SETTER_ARR(std::string, address, 3);
SETTER_OBJ_LR(std::string, name);
SETTER_OBJ_CLR(std::string, name);
SETTER_OBJ_RR(std::string, name);
GETTER_PRIM(int, id);
GETTER_FLAG(bool, merried);
GETTER_ENUM(human, type);
GETTER_OBJ_LR(std::string, name);
GETTER_OBJ_CLR(std::string, name);
GETTER_PTR(int, next);
GETTER_ARR(std::string, address);
private:
int id;
human type;
std::string name;
std::string address[5];
bool merried;
int* next;
};
Исходный код библиотечки с открытым кодом можно посмотреть вот по этой ссылке.