Комментарии 43
Почему бы cl##name ob##name тоже не сделать static?
Тогда populate_statdata и VcfInitializer будут не нужны
Тогда populate_statdata и VcfInitializer будут не нужны
+3
Приятная статья, спасибо!
+1
Еще можно добавить макрос DECLARE_MEMBER_PARAMSTRUCT_PTR для глубокого сравнения указателей
bool comp##name(const ThisParamFieldClass& a) const \
{ \
return (*name) == (*a.name); \
}
bool comp##name(const ThisParamFieldClass& a) const \
{ \
return (*name) == (*a.name); \
}
+1
Генерация кода C++. Описание структуры на C++ и оператора ее сравнения не пишется программистом вручную, а генерируется скриптом на основе описания структуры на каком-то другом входном языке. Данный подход, с моей точки зрения, является идеальным...
С моей точки зрения тоже. Почему вы отказались от такого решения?
0
Меня вот искренне удивляют такие статьи. Вопрос к автору: вы действительно готовы обьявлять параметры как
вместо
?
Просто, я бы не согласился с тем, что мешанина макросов в обьявлении структур данных лучше, чем написать руками оператор сравнения.
DECLARE_MEMBER_PARAMSTRUCT(double, karma);
вместо
double karma;
?
Просто, я бы не согласился с тем, что мешанина макросов в обьявлении структур данных лучше, чем написать руками оператор сравнения.
+4
Это до тех пор пока вы не потратите день пытаясь понять почему все перестало работать, хотя вы только добавили новую переменную в класс, но забыли внести ее в оператор сравнения. Разумеется такие баги редко выстреливают сразу же, поэтому обнаружится это через неделю, когда вы уже давно забудете о той переменной и честно будете искать ошибки в реализации двоичного поиска, или в других местах.
+1
Понимаю. Просто, дело в том, что если класс уже отлажен, то в него нельзя добавлять новые поля. Иначе, он автоматически становится неотлаженным. А для неотлаженных классов существует процесс тестирования. Если меняешь оттестированный код — протестируй его заново, я следую этому принципу, и у меня не может возникнуть описанной вами ситуации, отсюда мое недоумение.
0
скажем так, широкоизвестный макрос
никого не напрягает.
если же это необходимо для самописной ORM, или XML-сериализатора, то почему бы и нет
#define GETSET(type, name, name2) \
private: type name; \
public: TypeTraits<type>::ParameterType get##name2() const { return name; } \
void set##name2(TypeTraits<type>::ParameterType a##name2) { name = a##name2; }
никого не напрягает.
если же это необходимо для самописной ORM, или XML-сериализатора, то почему бы и нет
+1
Я пользуюсь этими макросами в нескольких своих проектах. Как только создал их — сразу почувствовал облегчение в работе. Главные аргументы я привел в статье. Это удобство и предохранение от ошибок. Член объявляется только в одном месте. Добавив или удалив член, не нужно менять расположенный в другом файле оператор сравнения. Ручное написание оператора почленного сравнения всегда имеет тот риск, что сравниваться будут не все члены. Забыть можно, проглядеть. Когда членов несколько десятков — то просто глазами трудно заметить, какой из них пропущен. А проявляться такой баг будет очень редко, как следствие — будет трудно его потом ловить.
Также я использую аналогичные макросы для автоматической генерации процедур загрузки и сохранения в файлы. Все три операции (сравнение, загрузка, сохранение) генерирует один вызов макроса. Так что, чисто по объему текста программы, получается даже экономия.
Также я использую аналогичные макросы для автоматической генерации процедур загрузки и сохранения в файлы. Все три операции (сравнение, загрузка, сохранение) генерирует один вызов макроса. Так что, чисто по объему текста программы, получается даже экономия.
0
НЛО прилетело и опубликовало эту надпись здесь
А что тут дебажить? Это структура данных. Отлаживать необходимо только сами макросы (которые уже отлажены). При отладке программы, использующей такие структуры, в отладчике VisualStudio видны члены данных. Служебные члены тоже видны, но за счет префиксов в их именах, разделить одни от других несложно. Сомневаюсь, что boost::fusion::map лучше визуализируются в отладчике.
И почему «дичайший» оверхед?
И почему «дичайший» оверхед?
+1
Всё-таки макросы зло, код стал весьма страшным на вид!
-2
Дичайше интересная статья.
Точно такой-же подход можно использовать, что-бы удобно и красиво реализовать проперти.
В принципе, от последнего макроса можно отказаться, если использовать не статические члены, а статические функции со статическими же переменными внутри.
ps. Очень жаль, что действительно интересная техничная статья имеет такой низкий рейтинг, хабр уже не торт
Точно такой-же подход можно использовать, что-бы удобно и красиво реализовать проперти.
В принципе, от последнего макроса можно отказаться, если использовать не статические члены, а статические функции со статическими же переменными внутри.
ps. Очень жаль, что действительно интересная техничная статья имеет такой низкий рейтинг, хабр уже не торт
0
Как человек не сильно испорченный плюсами рискну предложить альтернативный вариант:
В макрос завернуть не проблема.
Или я чего не знаю о хранении объектов в памяти?
bool testclass::operator==(testclass& comp)
{
int s = sizeof(*this);
for (int i = 0; i < s; i+=4)
{
if (*(int*)((int)this + i) != *(int*)((int)&comp + i)) return false;
}
return true;
}
В макрос завернуть не проблема.
Или я чего не знаю о хранении объектов в памяти?
-1
bool testclass::operator==(testclass& comp)
{
int s = sizeof(*this);
for (int i = 0; i < s; i+=4)
{
if (*(int*)((int)this + i) != *(int*)((int)&comp + i)) return false;
}
return true;
}
-1
Нда, не пишите так больше.
Даже не говоря о том, что будет если в структуре встретится к примеру что-нибудь типа std::string.
Это нарушает все нормы языка, и имеет кучую сайд эфектов.
Даже не говоря о том, что будет если в структуре встретится к примеру что-нибудь типа std::string.
Это нарушает все нормы языка, и имеет кучую сайд эфектов.
+1
Не думаю, что с std::string будет хуже, чем с double.
+1
С double как раз все отлично будет, а со стрингом, да, неочень.
Такая реализация накладывает много ограничений и больше подходит для С (не код а принцип), но и вариант автора далеко не универсален.
Такая реализация накладывает много ограничений и больше подходит для С (не код а принцип), но и вариант автора далеко не универсален.
0
Пожалуй, да. С double определённый таким образом порядок будет просто непресказуемым, а с чем-нибудь посложнее может меняться без видимого изменения данных во время программы между запусками/экземплярами.
0
с double будет плохо, потому что одно число в формате с плавающей точкой (мантисса-порядок) может имет разные двоичные предстваления. грубо, 3*10^6=30*10^5
но скорее глюков можно огрести на выравнивании. в структуре
между полями будет 3 байта для выравнивания, заполненных мусором.
их нужно исключить из сравнения.
но скорее глюков можно огрести на выравнивании. в структуре
struct MyClass {
char x;
int y;
};
между полями будет 3 байта для выравнивания, заполненных мусором.
их нужно исключить из сравнения.
0
Порядок получается нестественный.
0
Вы переусложнили. Итоговое решение (для пользователя) должно к такому сводиться:
struct MANPARAMS
{
std::string name;
int age;
std::vector<std::string> friend_names;
double karma;
GENERATE_COMPARE_FUNC(name, age, friend_names, karma);
};
+2
В предложенном Вами варианте члены структуры надо указывать в двух местах, что является потенциальным источником ошибок. Задача ставилась так, чтобы объявлять член только в одном месте.
0
Понимаете, вы «заставляете» каждый класс дополнительно нести кучу информации о его членах, а сама функция сравнения стала настолько сложной, что оптимизировать её компилятору уже невозможно (пройтись по динамическому массиву указателей на функции и вызвать их все — ни у какого компилятора AI не хватит).
Мой же вариант будет требовать нулевые накладные расходы: нет никаких дополнительных полей в классе, как следствие — нет дополнительных расходов на работу с ними в конструкторе/деструкторе. Кроме того, информация о типах полей известна компилятору — оператор сравнения будет сгенерирован очень эффективным для простых (базовых) типов.
Сравните то, что получается у вас для, к примеру, массива из тысячи элементов вида
И сравните с тем, что должно быть в идеале:
Мой же вариант будет требовать нулевые накладные расходы: нет никаких дополнительных полей в классе, как следствие — нет дополнительных расходов на работу с ними в конструкторе/деструкторе. Кроме того, информация о типах полей известна компилятору — оператор сравнения будет сгенерирован очень эффективным для простых (базовых) типов.
Сравните то, что получается у вас для, к примеру, массива из тысячи элементов вида
struct point_type {
int x, y;
};
— сколько ваше решение потребует выделений/освобождений памяти.И сравните с тем, что должно быть в идеале:
struct point_type {
int x, y;
bool operator==(const point_type& other) {
return x==other.x && y==other.y;
}
};
0
Идеал идеалу рознь. Понятное дело, что написанный вручную оператор сравнения быстрее, чем сгенерированный моими макросами. Тут разработчик стоит перед выбором, что ему важнее: скорость или удобство разработки и защищенность ее от ошибок на перспективу.
Хочу еще раз обратить ваше внимание, что накладные расходы у моих макросов очень низкие:
1) Информацию о членах несет не каждый экземпляр структуры, потому что эта информация хранится в статических членах, один экземпляр на всю программу.
2) Вследствие 1), конструкторы и деструкторы моих структур не занимаются инициализацией или освобождением массивов с информацией о структуре;
Имеющиеся накладные расходы:
1) На работу оператора сравнения — вызов функций по указателям;
2) При создании каждого экземпляра структуры — на проверку флага populate_statdata столько раз, сколько в структуре членов.
Больше накладных расходов нет. Мне кажется, что это вполне неплохой компромисс по сравнению с другими решениями, которые здесь рассматривались. К тому же, если объект содержит данные не простых типов, а сложные типы, контейнеры (string, map, vector и т.д.) — то при работе операторов сравнения именно сравнение членов будет доминировать во времени выполнения, а не проход по массиву с указателями на функции.
Ну и если описанные выше накладные расходы являются неприемлемыми — то и это еще не повод сдаваться и писать операторы сравнения вручную. Можно применить генерацию кода — я упомянул этот способ в статье.
Хочу еще раз обратить ваше внимание, что накладные расходы у моих макросов очень низкие:
1) Информацию о членах несет не каждый экземпляр структуры, потому что эта информация хранится в статических членах, один экземпляр на всю программу.
2) Вследствие 1), конструкторы и деструкторы моих структур не занимаются инициализацией или освобождением массивов с информацией о структуре;
Имеющиеся накладные расходы:
1) На работу оператора сравнения — вызов функций по указателям;
2) При создании каждого экземпляра структуры — на проверку флага populate_statdata столько раз, сколько в структуре членов.
Больше накладных расходов нет. Мне кажется, что это вполне неплохой компромисс по сравнению с другими решениями, которые здесь рассматривались. К тому же, если объект содержит данные не простых типов, а сложные типы, контейнеры (string, map, vector и т.д.) — то при работе операторов сравнения именно сравнение членов будет доминировать во времени выполнения, а не проход по массиву с указателями на функции.
Ну и если описанные выше накладные расходы являются неприемлемыми — то и это еще не повод сдаваться и писать операторы сравнения вручную. Можно применить генерацию кода — я упомянул этот способ в статье.
0
Я делал нечто подобное, но с использованием шаблонов.
Моей задачей было написание класса настроек с сериализацией в реестр или в файл.
Поля класса (структуры) я описывал при помощи шаблонных объектов-оболочек с общим базовым
интерфейсом (для хранения в общем списке).
При этом класс-оболочка вел себя как «умный» указатель, то есть имитировал стандартный синтаксис доступа к членам структуры.
В примитивном виде это было как-то так:
Каждый объект CValue в своем конструкторе регистрирует себя в общем списке m_values.
После чего список m_values можно использовать для обхода объектов с целью сравнения или сериализации (шаблон визитор).
В итоге получилась довольно интересная штуковина.
Моей задачей было написание класса настроек с сериализацией в реестр или в файл.
Поля класса (структуры) я описывал при помощи шаблонных объектов-оболочек с общим базовым
интерфейсом (для хранения в общем списке).
При этом класс-оболочка вел себя как «умный» указатель, то есть имитировал стандартный синтаксис доступа к членам структуры.
В примитивном виде это было как-то так:
template<typename T>
class CValue : public IValue
{...};
class CMyStruct
{
public:
CValue<int> id;
CValue<std::string> name;
private:
std::vector<IValue*> m_values;
};
Каждый объект CValue в своем конструкторе регистрирует себя в общем списке m_values.
После чего список m_values можно использовать для обхода объектов с целью сравнения или сериализации (шаблон визитор).
В итоге получилась довольно интересная штуковина.
+1
Спасибо, тоже интересное решение. А как у вас доступ к членам производится? Какой синтаксис?
0
За счет того, что в классе CValue были переопределены оператор преобразования к типу шаблонного параметра, а также оператор присвоения, к переменной класса CValue можно обращаться с применением стандартного синтаксиса через оператор "." либо "->".
Несколько интересней другой момент.
В моей реализации объекты класса CValue являются членами класса-структуры, которая в свою очередь содержит вектор указателей на базовый интерфейс IValue. И каждый класс CValue, при создании, должен себя регистрировать в этом векторе.
Но по законам C++ члены класса не имеют доступа к указателю на класс, в котором они содержаться.
А передавать в каждый член указатель на класс-контейнер не хотелось, т.к. каждый член пришлось бы писать дважды (при объявлении и в списке инициализации).
Этот момент я обыграл с использованием глобальных переменных-указателей, отдельных для каждого потока, что потребовало в свою очередь некоторой синхронизации:
Делалось все это ради простоты описания структуры, которое выглядит примерно так:
За счет того, что вначале отрабатывает конструктор базового класса, указатель на CStructBase* сохраняется в глобальной переменной, для текущего потока. Затем поочередно конструируются члены класса CMyStruct, и каждый из них регистрирует себя в списке m_values.
В общем решение немного не стандартное, но работает, если пользоваться аккуратно.
Кстати, Ваш способ с указателями на функции мне кажется более оптимальным, т.к. позволяет хранить лишь список статических данных, что лучше соответствует природе классов.
template<typename T>
class CValue : public IValue
{
public:
CValue& operator=(const T& rhs)
{
m_value = rhs;
return *this;
}
operator T()const
{
return m_value;
}
};
Несколько интересней другой момент.
В моей реализации объекты класса CValue являются членами класса-структуры, которая в свою очередь содержит вектор указателей на базовый интерфейс IValue. И каждый класс CValue, при создании, должен себя регистрировать в этом векторе.
Но по законам C++ члены класса не имеют доступа к указателю на класс, в котором они содержаться.
А передавать в каждый член указатель на класс-контейнер не хотелось, т.к. каждый член пришлось бы писать дважды (при объявлении и в списке инициализации).
Этот момент я обыграл с использованием глобальных переменных-указателей, отдельных для каждого потока, что потребовало в свою очередь некоторой синхронизации:
std::map<DWORD, CStructBase*> g_mapByThreadId;
CCriticalSection g_cs;
class IValue
{};
class CStructBase
{
public:
CStructBase()
{
CScopeLock scopeLock(g_cs);
g_mapByThreadId[GetCurrentThreadId()] = this;
}
static void RegisterValue(IValue* value)
{
CScopeLock scopeLock(g_cs);
g_mapByThreadId[GetCurrentThreadId()]->m_values.push_back(value);
}
private:
std::vector<IValue*> m_values;
};
class CValue : public IValue
{
public:
CValue()
{
CStructBase::RegisterValue(this);
}
};
Делалось все это ради простоты описания структуры, которое выглядит примерно так:
class CMyStruct : public CStructBase
{
public:
CValue<int> id;
CValue<std::string> name;
};
За счет того, что вначале отрабатывает конструктор базового класса, указатель на CStructBase* сохраняется в глобальной переменной, для текущего потока. Затем поочередно конструируются члены класса CMyStruct, и каждый из них регистрирует себя в списке m_values.
В общем решение немного не стандартное, но работает, если пользоваться аккуратно.
Кстати, Ваш способ с указателями на функции мне кажется более оптимальным, т.к. позволяет хранить лишь список статических данных, что лучше соответствует природе классов.
0
Спасибо за разъяснения. У вас тоже интересная конструкция получилась. Надо будет на досуге попробовать объединить идеи из моей и вашей реализации. Возможно, получится сделать систему с низкими затратами времени выполнения, но не на базе макросов, а на базе шаблонов.
0
не нужно городить с блокировками и мапой, когда в ОС есть поддержка thread-переменных на уровне ОС без блокировок и с минимальным оверхедом
в с++
__declspec(thread) CStructBase* currentClass;
в C#, Pascal и т.п. тоже есть аналогичные windows-specific кейворды ([ThreadStatic], threadvar)
в с++
__declspec(thread) CStructBase* currentClass;
в C#, Pascal и т.п. тоже есть аналогичные windows-specific кейворды ([ThreadStatic], threadvar)
+1
TLS конечно интересная вещь, но боюсь, что в ней много подводных камней, например только что нашел статью на RSDN:
LoadLibrary и __declspec(thread)
LoadLibrary и __declspec(thread)
+1
любопытный факт, не знал
0
Вот еще интересную статью нашел: TLS изнутри.
Оказывается есть две различные реализации TLS — динамическая и статическая.
А вообще считаю, что прежде чем использовать любую вещь в многопоточной среде, необходимо досконально разобраться как эта вещь устроена.
Оказывается есть две различные реализации TLS — динамическая и статическая.
А вообще считаю, что прежде чем использовать любую вещь в многопоточной среде, необходимо досконально разобраться как эта вещь устроена.
+1
да, и ещё проблема этого кода — создание CValue вне наследника CStructBase. тогда этот CValue допишется в последний созданный экземпляр CStructBase (который может быть уже разрушен).
по-хорошему, после завершения всех конструкторов членов CMyStruct надо сделать g_mapByThreadId[GetCurrentThreadId()] = NULL
что-то не соображу, как это сделать автоматически, чтобы не повторять в каждом конструкторе CMyStruct
по-хорошему, после завершения всех конструкторов членов CMyStruct надо сделать g_mapByThreadId[GetCurrentThreadId()] = NULL
что-то не соображу, как это сделать автоматически, чтобы не повторять в каждом конструкторе CMyStruct
0
А смысл, защита от дурака? В любом случае дурак напишет глючную программу.
В оригинальном коде класс CValue был объявлен как вложенный protected класс.
В оригинальном коде класс CValue был объявлен как вложенный protected класс.
0
я люблю assertions и пишу их часто.
наверное, я дурак ))
наверное, я дурак ))
0
А я не люблю асерты, наверное я тоже дурак :)
Я люблю по максимуму полагаться на compile-time и стараюсь писать код таким образом, чтобы код не собирался при неправильном использовании. Также я пишу юнит-тесты, которые запускаются при сборке проекта, и если хотя бы один тест не проходит — проект не собирается.
В общем все зависит от специфики конкретных проектов.
Я люблю по максимуму полагаться на compile-time и стараюсь писать код таким образом, чтобы код не собирался при неправильном использовании. Также я пишу юнит-тесты, которые запускаются при сборке проекта, и если хотя бы один тест не проходит — проект не собирается.
В общем все зависит от специфики конкретных проектов.
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Автоматическая генерация операторов сравнения структур в C++