Реализация свойств в С++

от автора

Всем привет! При реализации проектов разработчикам часто нужны классы, которые содержат только поля и не имеют никаких функций. Такие классы полезны для сохранения нужной информации с последующем их манипуляциями.

Первый подход в реализации таких классов основан на использовании С структур. Как недостаток этого подхода, это то что все поля доступные на запись и чтения, что не всегда хорошо.

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; }; 

Исходный код библиотечки с открытым кодом можно посмотреть вот по этой ссылке.


ссылка на оригинал статьи https://habr.com/ru/post/459212/