Вопрос:
Я работаю над фрагментом кода, который копирует объект Person из одного представления данных в другое. Имена в каждом классе (имя, адрес, заголовок) совпадают, а все типы – строки. Для каждого поля я хочу применить одно и то же преобразование на основе некоторого условия, которое также зависит от имени поля. Сложная часть состоит в том, что повторяющийся код использует суффиксы функций, основанные на имени поля. Это выглядит примерно так:
LibraryA::Person person1; LibraryB::Person person2; if (person1.name_valid() && […somestuff…]) { string *v = SomeOtherFunction(person.name()) person2.set_name(v); } if (person1.address_valid() && […somestuff…]) { string *v = SomeOtherFunction(person.address()) person2.set_address(v); } if (person1.title_valid() && […somestuff…]) { string *v = SomeOtherFunction(person.title()) person2.set_title(v); }
Есть ли трюк (или метод :)), чтобы разделить повторяющуюся часть на шаблон? Я бы предпочел решение, которое не связано с определением макроса (это было бы слишком просто :))
Лучший ответ:
Это соответствует вашим требованиям, но буду ли я использовать его или нет, это другой вопрос. Только если будет огромное количество повторений, я бы прошел через этот путь, а затем я бы объединил его с макросом, чтобы упростить код вызова:
void test_and_set( Person const & person1, Person & person2, bool (Person::*test)() const, std::string (Person::*get)() const, void (Person::*set)( std::string const &) ) { if ( (person1.*test)() ) { (person2.*set)( (person1.*get)() ); } }
Используется в качестве:
test_and_set( person1, person2, &Person::valid_name, &Person::get_name, &Person::set_name );
В сочетании с локальным макросом:
#define TEST_AND_SET( p1, p2, field ) test_and_set( (p1), (p2), &Person::valid_##field, &Person::get_##field, &Person::set_##field ) TEST_AND_SET( person1, person2, name ); TEST_AND_SET( person1, person2, title ); #undef TEST_AND_SET Ответ №1
Вы можете использовать указатель на функцию-член. Например (я не проверял, компилирует ли этот код):
typedef bool (LibraryA::Person::Validator)(); typedef string (LibraryA::Person::FieldGetter)(); typedef void (LibraryB::Person::FieldSetter)(string*); void ApplyField(LibraryA::Person& person1, LibraryB::Person& person2, Validator vl, FieldGetter get, FieldSetter set) { if (person1.vl() && […somestuff…]) { string* v = SomeOtherFunction(person1.get()); person2.set(v); } } ApplyField(person1, person2, &LibraryA::Person::name_valid, &LibraryA::Person::name, &LibraryB::Person::set_name); ApplyField(person1, person2, &LibraryA::Person::address_valid, &LibraryA::Person::address, &LibraryB::Person::set_address); ApplyField(person1, person2, &LibraryA::Person::title_valid, &LibraryA::Person::title, &LibraryB::Person::set_title);
Я не думаю, что шаблоны здесь подходят, потому что все поля одного типа. И я действительно не знаю, что у вас против макросов в этом случае. Вы можете использовать макрос для генерации вызова ApplyField(), если хотите.
Ответ №2
Был ли этот быстрый, конечно, недействительный C++, но я надеюсь, что вы поняли:
struct MyFunctor { Person *person1, *person2; void operator()(void Person::*validator(), string* Person::*getter(), void Person::*setter(string *)) { if (person1->*validator() && […somestuff…]) { string* v = SomeOtherFunction(person1->*getter()); person2->*setter(v); } } }; // Usage MyFunctor f = { person1, person2 }; f(&Person::name_valid, &Person::name, &Person::set_name); f(&Person::address_valid, &Person::address, &Person::set_address); f(&Person::title_valid, &Person::title, &Person::set_title); Ответ №3
Вы можете использовать массив объектов с указателем на элемент, заполнить его исходным и конечным точками преобразования, а затем применить преобразование к каждой записи в этом массиве. Это может выглядеть примерно так:
struct trans_info { trans_info(bool (S::*valid)() const, std::string* (S::*get)()const, void (T::*set)(std::string*)): valid_(valid), get_(get), set_(set) { } bool (S::*valid_)() const; std::string* (S::*get_)() const; void (S::*set_)(std::string*); }; trans_info const info[] = { trans_info(&S::name_valid, &S::name, &T::set_name), trans_info(&S::address_valid, &S::address, &T::set_address), trans_info(&S::title_valid, &S::title, &T::set_title), … }; template <typename T, int Size> T* begin(T (&array)[Size]) { return array; } template <typename T, int Size> T* end(T (&array)[Size]) { return array + Size; } transform(S const& person1, T& person2) { for (trans_info const* it(begin(info)), e(end(info)); it != end; ++it) { if ((person1.*(it->valid_))() && […somestuff…]) { string *v = SomeOtherFunction(person1.*(it->get_))()) (person2.*(it->set))(v); } } } Ответ №4
Если объекты человека неизменны для вас, вам не повезло.
Если это не так, укажите информацию из имени метода, используя классы тегов Person::Name, Person::Address и т.д., А затем переписывайте *_valid и *_set для использования перегрузки функций:
bool Person::is_valid( Name, std::string ) {…) bool Person::is_valid( Address, std::string ) {…) Ответ №5
Вы можете использовать аргументы указателя на элемент для шаблона.
Но я сомневаюсь в использовании указателей на строки, которые выглядят как вероятная утечка памяти.