2009-09-02 5 views
7

Проблема:Есть ли способ применить действие к членам класса N C++ в цикле над именами участников (возможно, через препроцессор)?

У меня есть класс C++ с gajillion (> 100) члены, которые ведут себя почти одинаково:

  • же типа

  • в функции, каждый член имеет такой же точный код, сделанный для него, как и другие участники, например присвоение с карты в конструкторе, где ключ карты совпадает с ключом ключа

  • Эта идентичность поведения повторяется по многим функциям (> 20), конечно, поведение в каждой функции различно, поэтому нет никакого способа фактор вещей.

  • Список членов очень жидкий, с постоянными дополнениями и иногда удалениями, некоторые (, но не все), управляемые изменением столбцов в таблице БД.

Как вы можете себе представить, это представляет большую боль в-за, насколько создание и поддержания кода, так как для добавления нового элемента, вы должны добавить код к каждой функции , где аналогичные члены являются используемый.

Пример решения я хотел бы

Actual C++ код мне нужно (скажем, в конструкторе):

MyClass::MyClass(SomeMap & map) { // construct an object from a map 
    intMember1 = map["intMember1"]; 
    intMember2 = map["intMember2"]; 
    ... // Up to 
    intMemberN = map["intMemberN"]; 
} 

C++ код, который я хочу, чтобы иметь возможность написать :

MyClass::MyClass(SomeMap & map) { // construct an object from a map 
#FOR_EACH_WORD Label ("intMember1", "intMember2", ... "intMemberN") 
    $Label = map["$Label"]; 
#END_FOR_EACH_WORD 
} 

Требования

  • Решение должно быть совместимо с GCC (с NMake как сделать систему, если это имеет значение). Не заботьтесь о других компиляторах.

  • Решение может быть на уровне предварительного процессора или что-то компилируемое. Я в порядке с одним; но до сих пор, все мои исследования указали меня к выводу, что последняя просто из невозможно в C++ (я так мисс Perl теперь, когда я вынужден сделать C++!)

  • Решение должно быть по крайней мере, в какой-то степени «отраслевой стандарт» (например, Boost отлично, но пользовательский скрипт Perl, созданный Joe-Quick-Fingers однажды и размещенный в его блоге, не является.Черт, я могу легко написать этот скрипт Perl, будучи гораздо более опытным специалистом по Perl, чем C++, - я просто не могу получить большие роли в Software Engineering на моей BigCompany, чтобы купить его используя :))

  • Решение должен позволить мне объявить список идентификаторов (в идеале, только в одном файле заголовка, а не в каждой директиве «#FOR_EACH_WORD», как это было в приведенном выше примере)

  • Решение не должно ограничиваться «созданием объекта из таблицы DB "конструктор. Существует много функций, большинство из которых не являются конструкторами, которым это необходимо.

  • Решение «Сделать все значения в одном векторе, а затем запустить цикл« за »по вектору» является очевидным и не может быть использовано - код в библиотеке, используемой многими приложениями , члены являются общедоступными, и, к сожалению, вопрос о том, что использовать эти приложения для использования членов векселя, а не названных членов, не может быть изменен.

+1

Не потейте языковой разницы. Perl выкопал бы вас из глупости C++, но организация может быть столь же глупой в Perl с такими же плохими результатами. –

+1

В ответ на ваш комментарий к ВОО. Если/когда вы решите реорганизовать этот код, взгляните на библиотеки «boost :: fusion» (возможно, на карте). В случае карты вы присоединяетесь к членам своего объекта, используя: «at_key < member1 > (объект)». Но преимущество в том, что вы можете выполнять итерацию через членов, используя что-то, основанное на «for_each». –

+0

Всем ответчикам/комментаторам - я очень ценю ваш вклад и усилия, как то, что может помочь мне улучшить код/​​задачу и стать источником отличного обучения. Я попытаюсь просмотреть все предложения и посмотреть, какие из них могут быть применимы, и в этот момент я выберу «принятый» ответ. – DVK

ответ

6

Boost.Preprocessor предлагает множество удобных макросов для выполнения таких операций. Bojan Resnik уже предоставил a solution, используя эту библиотеку, но предполагает, что каждое имя участника построено таким же образом.

Поскольку вы явно указали на возможность объявления списка идентификаторов, вот решение, которое должно наилучшим образом удовлетворить ваши потребности.

#include <boost/preprocessor/seq/for_each.hpp> 
#include <boost/preprocessor/stringize.hpp> 

// sequence of member names (can be declared in a separate header file) 
#define MEMBERS (foo)(bar) 

// macro for the map example 
#define GET_FROM_MAP(r, map, member) member = map[BOOST_PP_STRINGIZE(member)]; 

BOOST_PP_SEQ_FOR_EACH(GET_FROM_MAP, mymap, MEMBERS) 
// generates 
// foo = mymap["foo"]; bar = mymap["bar]; 

------- 

//Somewhere else, we need to print all the values on the standard output: 
#define PRINT(r, ostream, member) ostream << member << std::endl; 

BOOST_PP_SEQ_FOR_EACH(PRINT, std::cout, MEMBERS) 

Как вы можете видеть, вам просто нужно написать макрос, представляющий шаблон, который вы хотите повторить, и передать его в BOOST_PP_SEQ_FOR_EACH макросъемки.

+0

Вопрос: насколько хорошо это мастерство работает с GDB? Это не может быть решающим фактором, но я бы не хотел потерять способность правильно отлаживать. – DVK

+1

Мне никогда не приходилось отлаживать программу с помощью Boost.PP с GDB, поэтому я не могу правильно ответить на этот вопрос. Тем не менее, я знаю, что отладчик Visual Studio перешагивает макрос без его ввода ... Решением может быть компиляция и отладка предварительно обработанного файла (см. Gcc -E или cl.exe/P). Следует отметить, что BOOST_PP_SEQ_FOR_EACH - это макрос горизонтального повторения: все, что он создает, будет в одной строке. Вы можете проверить вертикальное повторение, что может облегчить отладку. –

0

Вы можете сделать что-то вроде его:

#define DOTHAT(m) m = map[#m] 
DOTHAT(member1); DOTHAT(member2); 
#undef DOTHAT 

Это не полностью соответствовать вашему описанию, но ближе всего к нему, что экономит печатаете.

4

Вы можете сделать что-то вроде этого: создать класс адаптера или изменить существующий класс, чтобы иметь вектор указателей на эти поля, добавить адреса всех переменных-членов в вопрос к этому вектору в конструкторе класса, а затем при необходимости запустите for-loop на этом векторе. Таким образом, вы не (или почти не меняете) меняете класс для внешних пользователей и имеете хорошие возможности для цикла.

+0

Мне очень нравится эта идея, хотя «почти» часть делает ее непригодной для использования здесь. мандат исключает любые изменения в приложениях, использующих этот класс. Интересно, могу ли я превратить текущий класс в класс адаптера? – DVK

+0

Технически вы можете, но вам придется перекомпилировать потребительские приложения, пока вы не покажете им только интерфейсы. – sharptooth

+2

Наличие вектора с указателями на члены, похоже, не требует изменения класса, в котором хранятся эти элементы. Таким образом, это похоже на решение, которое вы хотели иметь. Зачем вам нужно менять класс для внешних пользователей? –

2

Вы можете использовать препроцессор для определения членов, а затем использовать такое же определение доступа к ним:

#define MEMBERS\ 
    MEMBER(int, value)\ 
    SEP MEMBER(double, value2)\ 
    SEP MEMBER(std::string, value3)\ 

struct FluctuatingMembers { 
#define SEP ; 
#define MEMBER(type, name) type name 
MEMBERS 
#undef MEMBER 
#undef SEP 
}; 


.. client code: 
FluctuatingMembers f = { 1,2., "valuesofstringtype" }; 
std::cout << 
    #define SEP << 
    #define MEMBER(type, name) #name << ":" << f.##name 
    MEMBERS; 
    #undef MEMBER 
    #undef SEP 

Он работал для меня, но это трудно отлаживать.

+0

Вариант состоит в том, чтобы заменить MEMBERS на include (который может содержать #undefs). – AProgrammer

+0

хороший совет, чтобы добавить там undefs ... Хотя это как-то нарушает симметрию.черт возьми, это уродливое решение :) – xtofl

3

Сложно использовать perl на собственной машине для создания конструктора. Затем попросите увеличить зарплату, так как вы успешно поддерживаете такой огромный кусок кода.

+0

На самом деле, даже если вы имели в виду это как шутку, это идеальный способ решить эту проблему. Можно написать простой текстовый файл, в котором перечислены все члены таблицы (это может быть даже сгенерировано путем сброса схемы БД). Затем скрипт perl генерирует весь необходимый код. (+1). –

+0

Вы можете сделать то же самое с препроцессором c. – Alan

+0

Я не шутил. Полностью нет. Если боссы настолько узкие, что они запрещают perl, это идеальный способ как выполнить работу без работы, так и получить кредит. –

2

Вы также можете реализовать шаблон посетителя, основанный на указателе на элементы. После решения препроцессора этот способ выглядит более отлаживаемым.

struct FluctuatingMembers { 
    int v1; 
    double v2; 
    std::string v3; 
    template<typename Visitor> static void each_member(Visitor& v); 
}; 

template<typename Visitor> void FluctuatingMembers::each_member(Visitor& v) { 
    v.accept(&FluctuatingMembers::v1); 
    v.accept(&FluctuatingMembers::v2); 
    v.accept(&FluctuatingMembers::v3); 
} 


struct Printer { 
    FluctuatingMembers& f; 
    template< typename pt_member > void accept(pt_member m) const { 
     std::cout << (f::*m) << "\n"; 
    } 
}; 

// you can even use this approach for visiting 
// multiple objects simultaneously 
struct MemberComparer { 

    FluctuatingMembers& f1, &f2; 
    bool different; 
    MemberComparer(FluctuatingMembers& f1, FluctuatingMembers& f2) 
     : f1(f1),f2(f2) 
     ,different(false) 
    {} 

    template< typename pt_member > void accept(pt_member m) { 
     if((f1::*m) != (f2::*m)) different = true;   
    } 
}; 

... client code: 
FluctuatingMembers object1 = { 1, 2.2, "value2" } 
       , object2 = { 1, 2.2, "valuetoo" }; 

Comparer compare(object1, object2); 
FluctuatingMembers::each_member(compare); 
Printer pr = { object1 }; 
FluctuatingMembers::each_member(pr); 
+1

Вы объединили суть действительно приятного ответа с большим количеством шума. Синтаксис указателя на член предоставляет необходимую абстракцию, но привязка его к шаблону Visitor является отвлечением, когда другая общая конструкция может работать лучше. Для некоторого другого обсуждения синтаксиса указатель-член см. Http://stackoverflow.com/questions/1344840 – Novelocrat

+0

Этот ответ кажется слишком сложным: все члены имеют один и тот же тип, поэтому шаблон посетителя кажется быть переборщиком. Возможно, я что-то неправильно понял. –

+0

@litb: это правильно. Но если члены колеблются много, рано или поздно они станут двойниками и будут всплывать, и в этом случае этот подход достаточно общий. – xtofl

4

Конечно, очевидный вопрос: Почему у вас есть класс с 100 членами? Это действительно не кажется нормальным.

Предполагая, что это разумно, вы посмотрели boost preprocessor library? Я никогда не использовал его сам (как говорил один друг: это ведет к темной стороне), но из того, что я слышал, это должен быть инструмент для работы.

+0

Ответ: Это класс из унаследованного набора приложений; и перефразирование всей кровавой вещи не входит в список вещей, которые могут быть одобрены, чтобы провести время. – DVK

+0

BTW, я не уверен на 100%, что этот класс МОЖЕТ быть разработан с менее чем 100 членами, поскольку, как я уже упоминал, он во многом основан на таблице БД с 100 столбцами. Что не нормализуется к ставкам моего анализа (и не принадлежит моей команде в первую очередь :) – DVK

9

подталкивания включает в себя большую библиотеку препроцессора, которую можно использовать для создания такого кода:

#include <boost/preprocessor/repetition.hpp> 
#include <boost/preprocessor/stringize.hpp> 
#include <boost/preprocessor/cat.hpp> 

typedef std::map<std::string, int> SomeMap; 

class MyClass 
{ 
public: 
    int intMember1, intMember2, intMember3; 

    MyClass(SomeMap & map) 
    { 
     #define ASSIGN(z,n,_) BOOST_PP_CAT(intMember, n) = map[ BOOST_PP_STRINGIZE(BOOST_PP_CAT(intMember, n))]; 
     BOOST_PP_REPEAT_FROM_TO(1, 4, ASSIGN, nil) 
    } 
}; 
+0

Мы могли бы предположить, что у членов класса есть более значимые имена. Поэтому было бы невозможно использовать простой цикл. Я предлагаю использовать последовательность Boost.PP вместе с алгоритмом for_each. –

+1

Я только что увидел, что это явное требование OP: «Решение должно позволить мне объявить список идентификаторов». Я отправил ответ, показывающий, как это сделать, не стесняйтесь добавлять его к себе, чтобы получить более полный ответ. –

+0

Вы абсолютно правы - я отвечал утверждению OP о том, что данный код был «актуальным C++ кодом», который ему нужен. –

0

Возможно, что я хотел бы посмотреть, чтобы сделать было бы использовать во время выполнения полиморфизма (динамическая отправка). Создайте родительский класс для этих членов с помощью метода, который выполняет общие действия. Члены получают свой класс из этого родительского класса. Те, которые нуждаются в другой реализации метода, реализуют свои собственные. Если им тоже нужен общий материал, то внутри метода они могут сбрасывать базовый класс и вызывать его версию метода.

Тогда все, что вам нужно сделать в своем первоначальном классе, вызывает член для каждого метода.

0

Я рекомендовал бы небольшое приложение командной строки, написанные на любом языке, вы или ваша команда самых опытных в.

Добавить какой-то язык шаблонов для ваших исходных файлов. Для чего-то подобного вам не нужно реализовывать полноценный парсер или что-то подобное. Просто найдите легко идентифицируемый символ в начале строки и некоторые ключевые слова для замены.

Используйте приложение командной строки, чтобы преобразовать шаблонные исходные файлы в реальные исходные файлы. В большинстве систем сборки это должно быть легко сделать автоматически, добавив фазу сборки или просто сообщив системе сборки: «используйте MyParser.exe для обработки файлов типа * .tmp»

Вот пример того, что я говорю о:

MyClass.tmp

MyClass::MyClass(SomeMap & map) { // construct an object from a map 
▐REPLACE_EACH, LABEL, "intMember1", "intMember2, ... , "intMemberN" 
▐ LABEL = map["$Label"]; 
} 

Я использовал „▐“ в качестве примера, но любой символ, который бы в противном случае не появляются в качестве первого символа строки вполне приемлемо ,

Теперь вы будете обрабатывать эти .tmp-файлы в качестве исходных файлов и автоматически создавать код C++.

Если вы когда-нибудь слышали фразу «код записи, который записывает код», это то, что это значит :)

+0

Извините, но, как я сказал в требованиях к вопросу, (1), я могу легко написать свой perl preprocessed, но (2) полномочия, которые никогда не одобряют использование подобной вещи, подобной той, что используется в системе построения всей компании. В случае, если это не было явно очевидно, наличие моей собственной установки, которая предшествует процессам до регистрации CVS, не может быть и речи, поскольку это общий код, поддерживаемый многими разработчиками. – DVK

+0

Вы пишете препроцессор Perl. Затем вы запустите его и получите куски кода, который вы копируете в программу C++. Это способ спасти вас от написания кода спереди, вместо того, чтобы сделать этот Codezilla принципиально поддерживаемым (что вам запрещено делать). –

+0

@ DVK: Извините. Я пропустил это требование в вашем вопросе. Моя ошибка в том, что вы слишком быстро читаете его. Я думаю, что у Дэвида Торнли есть правильная идея. Даже если вы не можете заставить всю компанию использовать эту утилиту, вы все равно можете ее использовать, когда вам нужно поддерживать код. –

1

Почему бы не сделать это во время выполнения? (Я очень ненавижу макрос хакерами)

То, что вы действительно просите, в некотором смысле, это метаданные класса.

Так что я хотел бы попробовать что-то вроде:

class AMember{ 
...... 
}; 

class YourClass{ 
    AMember member1; 
    AMember member2; 
    .... 
    AMember memberN; 
    typedef AMember YourClass::* pMember_t; 
    struct MetaData : public std::vector<std::pair<std::string,pMember_t>>{ 
     MetaData(){ 
      push_back(std::make_pair(std::string("member1"),&YourClass::member1)); 
      ... 
      push_back(std::make_pair(std::string("memberN"),&YourClass::memberN)); 
     } 
    }; 

    static const MetaData& myMetaData() { 
     static const MetaData m;//initialized once 
     return m; 
    } 

    YourClass(const std::map<std::string,AMember>& m){ 
     const MetaData& md = myMetaData(); 
     for(MetaData::const_iterator i = md.begin();i!= md.end();++i){ 
      this->*(i->second) = m[i->first]; 
     } 
    } 
    YourClass(const std::vector<std::pair<std::string,pMember_t>>& m){ 
     const MetaData& md = myMetaData(); 
     for(MetaData::const_iterator i = md.begin();i!= md.end();++i){ 
      this->*(i->second) = m[i->first]; 
     } 
    } 
}; 

(довольно уверен, что я получил правильный синтаксис, но это техника пост не код пост)

RE: в функции , каждый член имеет тот же точный код, что и другие члены, например назначение с карты в конструкторе, где ключ карты совпадает с ключом ключа

это обрабатывается выше.

RE: Список членов очень жидкий, с постоянными дополнениями и иногда удалениями, некоторые (но не все), управляемые изменением столбцов в таблице БД.

При добавлении нового AMember, сказать newMember, все, что вам нужно сделать, это обновление метаданных конструктора с:

push_back(make_pair(std::string("newMember"),&YourClass::newMember)); 

RE: Это identicality поведения повторяются через много-много функций (> 20), разумеется, поведение в каждой функции различно, поэтому нет никакого способа опровергнуть ситуацию.

У вас есть машины, чтобы применить эту же идиома, чтобы построить функции

например: setAllValuesTo (Const AMember & значение)

YourClass::setAllValuesTo(const AMember& value){ 
    const MetaData& md = myMetaData(); 
    for(MetaData::const_iterator i = md.begin();i!= md.end();++i){ 
     this->*(i->second) = value; 
    } 
} 

Если вы чуть-чуть изобретательны с указателями на функции или шаблона функционалов, вы можете отбросить мутирующую операцию и сделать практически все, что захотите, для AM-членов YourClass в коллекции. Оберните эти общие функции (которые могут принять функциональный или функциональный указатель), чтобы реализовать ваш текущий набор из 20 общедоступных методов в интерфейсе.

Если вам нужно больше метаданных, просто добавьте кодомен от карты MetaData за указатель на элемент. (Конечно, i-> секундное значение изменилось бы тогда)

Надеюсь, это поможет.

0

Здесь уже есть много хороших ответов и идей, но ради разнообразия я расскажу другое.

В файле кода для MyClass будет:

struct MemberData 
{ 
    size_t Offset; 
    const char* ID; 
}; 

static const MemberData MyClassMembers[] = 
{ 
    { offsetof(MyClass, Member1), "Member1" }, 
    { offsetof(MyClass, Member2), "Member2" }, 
    { offsetof(MyClass, Member3), "Member3" }, 
}; 

size_t GetMemberCount(void) 
{ 
    return sizeof(MyClassMembers)/sizeof(MyClassMembers[0]); 
} 

const char* GetMemberID(size_t i) 
{ 
    return MyClassMembers[i].ID; 
} 

int* GetMemberPtr(MyClass* p, size_t i) const 
{ 
    return (int*)(((char*)p) + MyClassMembers[i].Offset); 
} 

Что тогда делает возможным написать нужный конструктор, как:

MyClass::MyClass(SomeMap& Map) 
{ 
    for(size_t i=0; i<GetMemberCount(); ++i) 
    { 
     *GetMemberPtr(i) = Map[GetMemberID(i)]; 
    } 
} 

И, конечно же, для любых других функций, работающих на всех члены, которых вы пишете аналогичные циклы.

В настоящее время существует несколько проблем с этой техникой:

  • Операции над членами использовать цикл выполнения, в отличие от других решений, которые дали бы в развернутую последовательность операций.
  • Это абсолютно зависит от каждого члена, имеющего тот же тип. Хотя это было разрешено ОП, все равно следует оценить, может ли это измениться в будущем. Некоторые из других решений не имеют этого ограничения.
  • Если я правильно помню, offsetof определяется только для работы с типами POD по стандарту C++. На практике я никогда не видел, чтобы это терпело неудачу. Однако я не использовал все компиляторы C++. В частности, я никогда не использовал GCC. Поэтому вам нужно будет протестировать это в своей среде, чтобы убедиться, что оно действительно работает по назначению.

Независимо от того, какие из этих проблем являются проблемами, вы должны будете оценить свою собственную ситуацию.


Теперь, считая, что этот метод применим, есть одно приятное преимущество. Эти функции GetMemberX можно превратить в общедоступные статические/членные функции вашего класса, тем самым предоставив этому универсальному члену доступ ко многим местам вашего кода.

class MyClass 
{ 
public: 
    MyClass(SomeMap& Map); 

    int Member1; 
    int Member2; 
    int Member3; 

    static size_t GetMemberCount(void); 
    static const char* GetMemberID(size_t i); 
    int* GetMemberPtr(size_t i) const; 
}; 

И если полезно, вы можете также добавить GetMemberPtrByID функцию для поиска заданной строки ID и возвращает указатель на соответствующий элемент.


Одним из недостатков этой идеи до сих пор является то, что существует риск того, что член может быть добавлен к классу, но не в массив MyClassMembers. Однако этот метод можно комбинировать с решением макросов xtofl, чтобы один список мог заполнять как класс, так и массив.

изменения в заголовке:

#define MEMBERS\ 
    MEMBER(Member1)\ 
    SEP MEMBER(Member2)\ 
    SEP MEMBER(Member3)\ 

class MyClass 
{ 
public: 
    #define SEP ; 
    #define MEMBER(name) int name 
    MEMBERS; 
    #undef MEMBER 
    #undef SEP 

    // other stuff, member functions, etc 
}; 

и изменения в файле кода:

const MemberData MyClassMembers[] = 
{ 
    #define SEP , 
    #define MEMBER(name) { offsetof(MyClass, name), #name } 
    MEMBERS 
    #undef MEMBER 
    #undef SEP 
}; 

Примечание: Я оставил проверку ошибок из моих примеров здесь. В зависимости от того, как это будет использоваться, вы можете захотеть убедиться, что границы массива не переполнены с режимами отладки и/или режимами выпуска, которые вернут указатели NULL для плохих индексов. Или, при необходимости, использование исключений.

Конечно, если вы не беспокоитесь об ошибке проверки границ массива, то GetMemberPtr действительно может быть изменено на что-то другое, которое вернет ссылку на участника.