Страница 1 из 2
Какой вариант лучше для обхода полей класса
Добавлено: 03 ноя 2016, 14:19
Din666
Думаю многим было бы интересно реализовать структуру/класс, в котором можно было бы обращаться к членам класса напрямую, но и при необходимости
циклом обойти их, чтобы сделать какие-то действия.
Код: Выделить всё
// нужен обход полей класса, объект класса должен быть копируемый и с move семантикой
// какой вариант с member pointer лучше по красоте, поддержке, скорости ....
// а может есть какой-нить третий ?
#include <string>
#include <unordered_map>
#include <iostream>
#include <functional>
struct Iface {
virtual void print() const = 0;
virtual ~Iface() {}
};
template <class T> struct Realize final : public Iface {
Realize(
const std::string & name
, const T & val = T()
) : name(name), val(val) {}
std::string name; // < исправлено - удалено const std::string name;
T val;
inline void print() const override {
std::cout << "name=" << name << " val=" << val << std::endl;
}
};
struct Usage {
#define VAR(name, default_value) m_##name(#name, default_value)
Usage() : VAR(Var1, 123), VAR(Var2, "test") {}
Realize<int> m_Var1;
Realize<std::string> m_Var2;
// 1 вариант
static const std::unordered_map<std::string, const Iface Usage::*> m_allVars1;
inline void print1() const {
for (const auto & each : m_allVars1) { (this->*(each.second)).print(); }
}
// 2 вариант
using getMember = const Iface & (* const)(const Usage &);
static const std::unordered_map<std::string, getMember> m_allVars2;
inline void print2() const {
for (const auto & each : m_allVars2) { each.second(*this).print(); }
}
};
// 1 вариант
const decltype(Usage::m_allVars1) Usage::m_allVars1 = {
// { "Var1", &Usage::m_Var1 } // error: could not convert ‘{&Usage::m_Var1}’ from ‘<brace-enclosed initializer list>’ to ‘const std::vector<Iface Usage::*>’
#define ELEMENT(name) { #name, reinterpret_cast<Iface Usage::*>(&Usage::m_##name) } // OK но так стремно
ELEMENT(Var1)
, ELEMENT(Var2)
};
// 2 вариант
const decltype(Usage::m_allVars2) Usage::m_allVars2 = {
#undef ELEMENT // )))
#define ELEMENT(name) { #name, [](const Usage &s) -> const Iface & {return s.m_##name;} }
ELEMENT(Var1)
, ELEMENT(Var2)
};
int main() {
Usage usage;
usage.print1();
usage.print2();
return 0;
}
Re: какой вариант лучше для обхода полей класса
Добавлено: 03 ноя 2016, 17:33
Romeo
Использование unordered_map неоправдано, так как мы нигде не используем поиск. Да и с типами ты что-то намудрил: как по-мне можно сделать всё проще.
Вот мой вариант, набросанный на скорую руку.
Код: Выделить всё
#include <iostream>
#include <vector>
namespace meta
{
struct FieldBase
{
virtual void Print() const = 0;
};
class StructBase;
template <class Type>
class Field : public FieldBase
{
public:
Field(const char* name, StructBase* strct);
virtual void Print() const override;
Field& operator=(const Type& value);
operator Type() const;
Type* operator &() const;
private:
const char* m_name;
Type m_value;
};
class StructBase
{
public:
StructBase();
void AddField(const FieldBase* field);
void Print() const;
private:
std::vector<const FieldBase*> m_fields;
};
template <class Type>
Field<Type>::Field(const char* name, StructBase* strct) :
m_name(name), m_value()
{
strct->AddField(this);
}
template <class Type>
void Field<Type>::Print() const
{
std::cout << "name=" << m_name << " value=" << m_value << std::endl;
}
template <class Type>
Field<Type>& Field<Type>: :o perator=(const Type& value)
{
m_value = value;
return *this;
}
template <class Type>
Field<Type>: :o perator Type() const
{
return m_value;
}
template <class Type>
Type* Field<Type>: :o perator &() const
{
return &m_value;
}
StructBase::StructBase()
{
}
void StructBase::AddField(const FieldBase* field)
{
m_fields.push_back(field);
}
void StructBase::Print() const
{
for (auto field : m_fields)
{
field->Print();
}
}
} // namespace meta
#define DECLARE_STRUCT_BEGIN(NAME) struct NAME : public meta::StructBase {
#define DECLARE_STRUCT_END };
#define DECLARE_STRUCT_CONSTRUCTION_BEGIN(NAME) NAME() : meta::StructBase()
#define DECLARE_STRUCT_CONSTRUCTION_END {}
#define DECLARE_FIELD_CONSTRUCTION(NAME) , m_##NAME(#NAME, this)
#define DECLARE_FIELD(NAME, TYPE) meta::Field< TYPE > m_##NAME;
///////////////////////////////////////////////////////////////////////////
DECLARE_STRUCT_BEGIN(MyStruct)
DECLARE_STRUCT_CONSTRUCTION_BEGIN(MyStruct)
DECLARE_FIELD_CONSTRUCTION(Var1)
DECLARE_FIELD_CONSTRUCTION(Var2)
DECLARE_STRUCT_CONSTRUCTION_END
DECLARE_FIELD(Var1, int)
DECLARE_FIELD(Var2, std::string)
DECLARE_STRUCT_END
int main()
{
MyStruct my;
my.m_Var1 = 5;
my.m_Var2 = "Test";
my.Print();
return 0;
}
Re: какой вариант лучше для обхода полей класса
Добавлено: 07 ноя 2016, 10:40
Din666
Спасибо за ответ. В данном контексте ты прав насчет unordered_map, но на самом деле поиск по строковому типу нужен - это система конфига, нужен поиск и прямое обращение к полям класса и многое чего другое (в том числе все операторы из твоего варианта). Я для простоты не стал это показывать, чтобы была видна основная проблема. Насчет мув семантики у меня ошибка - 21 срока const std::string name; - не надо конст. А ты действительно считаешь оправданным такое количество макросов в твоем варианте? А самое главное у тебя нехватает конструктора копирования и мув, оператора присваивания. При копировании указатели в векторе станут невалидные, т.к. это не смещения относительно структуры класса, а просто внешние указатели.
std::vector<const FieldBase*> m_fields - неверно; надо std::vector<const FieldBase StructBase::*> m_fields;
Re: Какой вариант лучше для обхода полей класса
Добавлено: 07 ноя 2016, 21:46
Romeo
- Даже если поиск нужен, я бы всё равно использовал либо вектор пар, либо в крайнем случае std::map. Использование unordered_map ведёт к чудовищному оверхеду и оправдано только на больших массивах данных. Полей же в структуре будет ну десяток, ну два десятка от силы. При таких количествах обычно даже map не используют, а обходятся вектором пар, так как перебор даёт всего на несколько итераций больше, чем бинарный поиск, в то время как на построении контейнера мы теряем все мизерные бонусы, которые заработали на поиске, да ещё и уходим в минус, причём прилично.
- В твоём коде в любом случае нужно убирать const у std::string, так как не будет работать не только мув семантика, но и оператор присваивания. По логике и так понятно, что name должен изменяться. Снимай const и прячь его в private, если не хочешь, чтобы его менял кто-то снаружи.
- Да, то, что структура должна копироваться я не учитывал, причём обдуманно. Причина та же, что и у тебя с unordered_map: хотел показать сам принцип, не загружая всё очевидным кодом, в котором суть утонет. Да, для меня код конструкторов и оператора присваивания очевиден - мог бы не объяснять, как работает shallow copy.
- Макросы не обязательны, но, как по мне, вполне оправданы. Они позволяют не делать одни и те же вещи снова и снова, а так же берегут от неправильного использования и опечаток (например, без использования макроса можно указать имя переменной одно, а name другой, а с макросом не получится).
- На счёт последнего исправления я вообще не понял, зачем оно нужно. У меня всё работает именно так, как я написал. Более того, я не знаю, что этот синтаксис обозначает (использование оператора доступа для звёздочки).
Re: какой вариант лучше для обхода полей класса
Добавлено: 08 ноя 2016, 00:07
Romeo
Полный вариант со всеми конструкторами и операторами и тестами на них.
Код: Выделить всё
#include <iostream>
#include <vector>
namespace meta
{
struct FieldBase
{
virtual void Print() const = 0;
};
class StructBase;
template <class Type>
class Field : public FieldBase
{
public:
Field(const Field& rhs) = delete;
Field(Field&& rhs) = delete;
Field& operator=(const Field& rhs) = delete;
Field& operator=(Field&& rhs) = delete;
Field(const char* name, StructBase* strct);
Field(const Field& rhs, StructBase* strct);
Field(Field&& rhs, StructBase* strct);
void Assign(const Field& rhs, StructBase* strct);
void Assign(Field&& rhs, StructBase* strct);
Field& operator=(const Type& value);
operator Type() const;
Type* Addr() const;
// FieldBase overrides
virtual void Print() const override;
private:
const char* m_name;
Type m_value;
};
class StructBase
{
public:
StructBase();
StructBase(const StructBase& rhs) = delete;
StructBase(StructBase&& rhs) = delete;
StructBase& operator=(const StructBase& rhs) = delete;
StructBase& operator=(StructBase&& rhs) = delete;
void AddField(const FieldBase* field);
void ClearFields();
void Print() const;
private:
std::vector<const FieldBase*> m_fields;
};
template <class Type>
Field<Type>::Field(const char* name, StructBase* strct) :
m_name(name), m_value()
{
strct->AddField(this);
}
template <class Type>
Field<Type>::Field(const Field<Type>& rhs, StructBase* strct) :
m_name(rhs.m_name), m_value(rhs.m_value)
{
strct->AddField(this);
}
template <class Type>
Field<Type>::Field(Field<Type>&& rhs, StructBase* strct) :
m_name(rhs.m_name), m_value(std::move(rhs.m_value))
{
strct->AddField(this);
rhs.m_name = nullptr;
}
template <class Type>
void Field<Type>::Assign(const Field<Type>& rhs, StructBase* strct)
{
if (&rhs != this)
{
m_name = rhs.m_name;
m_value = rhs.m_value;
strct->AddField(this);
}
}
template <class Type>
void Field<Type>::Assign(Field<Type>&& rhs, StructBase* strct)
{
if (&rhs != this)
{
m_name = rhs.m_name;
rhs.m_name = nullptr;
m_value = std::move(rhs.m_value);
strct->AddField(this);
}
}
template <class Type>
Field<Type>& Field<Type>: :o perator=(const Type& value)
{
m_value = value;
return *this;
}
template <class Type>
Field<Type>: :o perator Type() const
{
return m_value;
}
template <class Type>
Type* Field<Type>::Addr() const
{
return &m_value;
}
template <class Type>
void Field<Type>::Print() const
{
std::cout << "name=" << m_name << " value=" << m_value << std::endl;
}
StructBase::StructBase() : m_fields()
{
}
void StructBase::AddField(const FieldBase* field)
{
m_fields.push_back(field);
}
void StructBase::ClearFields()
{
m_fields.clear();
}
void StructBase::Print() const
{
for (auto field : m_fields)
{
field->Print();
}
}
} // namespace meta
#define DECLARE_STRUCT_BEGIN(NAME) struct NAME : public meta::StructBase {
#define DECLARE_STRUCT_END };
#define DECLARE_STRUCT_CONSTRUCTION_BEGIN(NAME) NAME() : meta::StructBase()
#define DECLARE_STRUCT_CONSTRUCTION_END {}
#define DECLARE_STRUCT_COPYING_BEGIN(NAME) NAME(const NAME& rhs) : meta::StructBase()
#define DECLARE_STRUCT_COPYING_END {}
#define DECLARE_STRUCT_MOVE_COPYING_BEGIN(NAME) NAME(NAME&& rhs) : meta::StructBase()
#define DECLARE_STRUCT_MOVE_COPYING_END { rhs.ClearFields(); }
#define DECLARE_STRUCT_ASSIGNMENT_BEGIN(NAME) NAME& operator=(const NAME& rhs) { if (&rhs != this) { ClearFields();
#define DECLARE_STRUCT_ASSIGNMENT_END } }
#define DECLARE_STRUCT_MOVE_ASSIGNMENT_BEGIN(NAME) NAME& operator=(NAME&& rhs) { if (&rhs != this) { ClearFields();
#define DECLARE_STRUCT_MOVE_ASSIGNMENT_END rhs.ClearFields(); } }
#define DECLARE_FIELD_CONSTRUCTION(NAME) , m_##NAME(#NAME, this)
#define DECLARE_FIELD_COPYING(NAME) , m_##NAME(rhs.m_##NAME, this)
#define DECLARE_FIELD_MOVE_COPYING(NAME) , m_##NAME(std::move(rhs.m_##NAME), this)
#define DECLARE_FIELD_ASSIGNMENT(NAME) m_##NAME.Assign(rhs.m_##NAME, this);
#define DECLARE_FIELD_MOVE_ASSIGNMENT(NAME) m_##NAME.Assign(std::move(rhs.m_##NAME), this);
#define DECLARE_FIELD(NAME, TYPE) meta::Field< TYPE > m_##NAME;
Re: Какой вариант лучше для обхода полей класса
Добавлено: 08 ноя 2016, 00:07
Romeo
Код: Выделить всё
DECLARE_STRUCT_BEGIN(MyStruct)
DECLARE_STRUCT_CONSTRUCTION_BEGIN(MyStruct)
DECLARE_FIELD_CONSTRUCTION(Var1)
DECLARE_FIELD_CONSTRUCTION(Var2)
DECLARE_STRUCT_CONSTRUCTION_END
DECLARE_STRUCT_COPYING_BEGIN(MyStruct)
DECLARE_FIELD_COPYING(Var1)
DECLARE_FIELD_COPYING(Var2)
DECLARE_STRUCT_COPYING_END
DECLARE_STRUCT_MOVE_COPYING_BEGIN(MyStruct)
DECLARE_FIELD_MOVE_COPYING(Var1)
DECLARE_FIELD_MOVE_COPYING(Var2)
DECLARE_STRUCT_MOVE_COPYING_END
DECLARE_STRUCT_ASSIGNMENT_BEGIN(MyStruct)
DECLARE_FIELD_ASSIGNMENT(Var1)
DECLARE_FIELD_ASSIGNMENT(Var2)
DECLARE_STRUCT_ASSIGNMENT_END
DECLARE_STRUCT_MOVE_ASSIGNMENT_BEGIN(MyStruct)
DECLARE_FIELD_MOVE_ASSIGNMENT(Var1)
DECLARE_FIELD_MOVE_ASSIGNMENT(Var2)
DECLARE_STRUCT_MOVE_ASSIGNMENT_END
DECLARE_FIELD(Var1, int)
DECLARE_FIELD(Var2, std::string)
DECLARE_STRUCT_END
int main()
{
MyStruct my;
my.m_Var1 = 5;
my.m_Var2 = "Test";
std::cout << "Structure my:" << std::endl;
my.Print();
std::cout << std::endl;
MyStruct my2 = my;
std::cout << "Structure my copied to my2." << std::endl;
std::cout << "Structure my2:" << std::endl;
my2.Print();
std::cout << std::endl;
MyStruct my3 = std::move(my2);
std::cout << "Structure my2 moved to my3." << std::endl;
std::cout << "Structure my2:" << std::endl;
my2.Print();
std::cout << "Structure my3:" << std::endl;
my3.Print();
std::cout << std::endl;
MyStruct another_my;
another_my.m_Var1 = 10;
another_my.m_Var2 = "Foo";
std::cout << "Structure another_my:" << std::endl;
another_my.Print();
std::cout << std::endl;
my2 = another_my;
std::cout << "Structure another_my assgined to my2." << std::endl;
std::cout << "Structure my2:" << std::endl;
my2.Print();
std::cout << std::endl;
my3 = std::move(my2);
std::cout << "Structure my2 moved by assignment to my3." << std::endl;
std::cout << "Structure my2:" << std::endl;
my2.Print();
std::cout << "Structure my3:" << std::endl;
my3.Print();
std::cout << std::endl;
return 0;
}
Re: Какой вариант лучше для обхода полей класса
Добавлено: 08 ноя 2016, 10:52
Din666
Думаю совместить оба решения и получится конфетка. В моем варианте хорошо что мне не надо копировать каждый раз static const std::unordered_map<std::string, const Iface Usage::*> m_allVars1; - внимание статик конст !!! ))). а в твоем варианте хорошие макросы, но много лишнего кода. конструкторы(copy,mov),и op= создаются автоматом если не делать руками хотябы один, а у меня переменные с одинаковыми названиями раскиданы по коду, что не есть хорошо
>> - На счёт последнего исправления я вообще не понял, зачем оно нужно. ........ Более того, я не знаю, что этот синтаксис обозначает
это указатель на поле класса
то есть смещение относительно начала класса, которое постоянное в скомпилированном объекте, это и можно использоваться для инита один раз
http://en.cppreference.com/w/cpp/language/pointer
кратко:
Код: Выделить всё
struct C { int m; };
main {
int C::* p = &C::m; // pointer to data member m of class C
C c = {7};
std::cout << c.*p << '\n'; // prints 7
}
Спасибо !!
Re: Какой вариант лучше для обхода полей класса
Добавлено: 08 ноя 2016, 17:11
Romeo
Я что-то не сообразил сразу использовать указатель на мембер. Если честно, я вообще не вспомнил, что так можно делать. Вполне возможно, что это действительно упростит код код. Вечерком обдумаю всё и выложу код, максимально оптимальный и лаконичный с моей точки зрения. Потом и "сверим часы"

Re: Какой вариант лучше для обхода полей класса
Добавлено: 09 ноя 2016, 00:59
Romeo
В общем, вот окончательный вариант, который кажется мне самым оптимальным/кратким.
Код: Выделить всё
#include <iostream>
#include <vector>
#include <string>
namespace meta
{
struct FieldBase
{
virtual void Print() const = 0;
};
template <class Type>
class Field : public FieldBase
{
public:
Field(const char* name);
Field(const char* name, const Type& value);
Field& operator=(const Type& value);
Field& operator=(Type&& value);
operator Type() const;
Type* operator&() const;
// FieldBase overrides
virtual void Print() const override;
private:
const char* m_name;
Type m_value;
};
template <class Type>
Field<Type>::Field(const char* name) :
m_name(name), m_value()
{
}
template <class Type>
Field<Type>::Field(const char* name, const Type& value) :
m_name(name), m_value(value)
{
}
template <class Type>
Field<Type>& Field<Type>: :o perator=(const Type& value)
{
m_value = value;
return *this;
}
template <class Type>
Field<Type>& Field<Type>: :o perator=(Type&& value)
{
m_value = std::move(value);
return *this;
}
template <class Type>
Field<Type>: :o perator Type() const
{
return m_value;
}
template <class Type>
Type* Field<Type>: :o perator&() const
{
return &m_value;
}
template <class Type>
void Field<Type>::Print() const
{
std::cout << "name=" << m_name << " value=" << m_value << std::endl;
}
// Fake structure to avoid syntax error in constructor of an user structure.
class StructBase {};
} // namespace meta
#define DECLARE_STRUCT_BEGIN(NAME) struct NAME : public meta::StructBase { \
private: \
static const meta::FieldBase NAME::* sm_field_ptrs_array[]; \
\
public: \
void Print() const;
#define DECLARE_STRUCT_END };
#define DECLARE_STRUCT_CONSTRUCTION_BEGIN(NAME) NAME() : meta::StructBase()
#define DECLARE_STRUCT_CONSTRUCTION_END {}
#define DECLARE_FIELDS_MAPING_BEGIN(NAME) \
const meta::FieldBase NAME::* NAME::sm_field_ptrs_array[] = \
{
#define DECLARE_FIELD_MAPPING(STRUCT_NAME, FIELD_NAME) \
reinterpret_cast<meta::FieldBase STRUCT_NAME::*>(&STRUCT_NAME::m_##FIELD_NAME),
#define DECLARE_FIELDS_MAPING_END(NAME) \
}; \
\
void NAME::Print() const \
{ \
const auto size = sizeof(sm_field_ptrs_array)/sizeof(*sm_field_ptrs_array); \
for (auto i = 0; i < size; ++i) \
{ \
(this->*sm_field_ptrs_array[i]).Print(); \
} \
}
#define DECLARE_FIELD_CONSTRUCTION(NAME) , m_##NAME(#NAME)
#define DECLARE_FIELD_CONSTRUCTION_DEF(NAME, VALUE) , m_##NAME(#NAME, VALUE)
#define DECLARE_FIELD(NAME, TYPE) meta::Field< TYPE > m_##NAME;
///////////////////////////////////////////////////////////////////////////
DECLARE_STRUCT_BEGIN(MyStruct)
DECLARE_STRUCT_CONSTRUCTION_BEGIN(MyStruct)
DECLARE_FIELD_CONSTRUCTION(Var1)
DECLARE_FIELD_CONSTRUCTION(Var2)
DECLARE_STRUCT_CONSTRUCTION_END
DECLARE_FIELD(Var1, int)
DECLARE_FIELD(Var2, std::string)
DECLARE_STRUCT_END
DECLARE_FIELDS_MAPING_BEGIN(MyStruct)
DECLARE_FIELD_MAPPING(MyStruct, Var1)
DECLARE_FIELD_MAPPING(MyStruct, Var2)
DECLARE_FIELDS_MAPING_END(MyStruct)
int main()
{
MyStruct my;
my.m_Var1 = 5;
my.m_Var2 = std::string("Test");
std::cout << "Structure my:" << std::endl;
my.Print();
std::cout << std::endl;
MyStruct my2 = my;
std::cout << "Structure my copied to my2." << std::endl;
std::cout << "Structure my2:" << std::endl;
my2.Print();
std::cout << std::endl;
MyStruct my3 = std::move(my2);
std::cout << "Structure my2 moved to my3." << std::endl;
std::cout << "Structure my2:" << std::endl;
my2.Print();
std::cout << "Structure my3:" << std::endl;
my3.Print();
std::cout << std::endl;
MyStruct another_my;
another_my.m_Var1 = 10;
another_my.m_Var2 = std::string("Foo");
std::cout << "Structure another_my:" << std::endl;
another_my.Print();
std::cout << std::endl;
my2 = another_my;
std::cout << "Structure another_my assgined to my2." << std::endl;
std::cout << "Structure my2:" << std::endl;
my2.Print();
std::cout << std::endl;
my3 = std::move(my2);
std::cout << "Structure my2 moved by assignment to my3." << std::endl;
std::cout << "Structure my2:" << std::endl;
my2.Print();
std::cout << "Structure my3:" << std::endl;
my3.Print();
std::cout << std::endl;
return 0;
}
В качестве итога скажу следующее. Использование pointer-to-member действительно позволяет сильно упростить код. В первую очередь это позволяет избавиться от контейнера со ссылками/указателями на поля в объекте, заменив одним статическим контейнером на каждую пользовательскую структуру. Удаление контейнера из объекта влечёт за собой упрощение стандартных конструкторов/операторов до уровня генерируемых, так что нужда в их ручной имплементации отпадает, что ещё больше сжимает код.
P.S. А вот от использования unordered_map я бы всё-таки рекомендовал тебе уйти. Рассуждения о причинах приведены несколькими сообщениями выше.
Re: Какой вариант лучше для обхода полей класса
Добавлено: 09 ноя 2016, 11:00
Din666
OK. насчет unordered_map буду иметь ввиду когда придется делать много копирований контейнера. А что скажешь насчет второго моего варианта на лямддах?
В продакшн коде я выбрал именно вариант с лямбдами, чтобы без реинтерпрет_каста.
кстати Field<Type>:

perator Type() const ---> Field<Type>:

perator Type&() const - чтобы не было копирования - конст же все равно, + virtual ~FieldBase() {}
// вроде интересная тема, странно что больше никто не участвует ((