Pull to refresh

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

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

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

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

Исходный код библиотечки с открытым кодом можно посмотреть вот по этой ссылке.
Tags:
Hubs:
Total votes 19: ↑7 and ↓12-5
Comments35

Articles