有应用在一个循环过会员名称的动作到N C ++类成员(可能是通过预处理器)的方法吗?(Is ther

2019-08-03 11:07发布

问题:

我有gajillion C ++类(> 100),其行为几乎相同成员:

  • 同一类型

  • 在一个功能中,每个构件具有完成它作为其他成员完全相同的代码,从一个构造的地图例如分配,其中地图键是相同成员键

  • 这种行为同一性跨多对多功能(> 20)重复,当然每个函数的行为是不同的,所以没有办法因素的事情了。

  • 成员名单是非常流畅,与在数据库表中更改列驱动的不断增加,有时甚至缺失,一些( 但不是全部 )。

你可以想像,这提出了一个很大的痛苦,在最落后尽可能代码的创建和维护,因为增加你必须将代码添加到每一个地方类似的成员使用的功能的新成员。

的溶液的例子我想

实际的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出不可能++(我所以现在我不得不做C小姐回了Perl的++!)

  • 该解决方案必须至少在一定程度上“行业标准”(例如升压是伟大的,但乔 - 快速手指创建一次,并公布在他的博客自定义Perl脚本是没有的。哎呀,我可以很容易地编写Perl脚本,多胜少一个Perl的专家比C ++之一 - 我不能在我的BigCompany获得软件工程中的大佬们购买到使用它:))

  • 该解决方案应该允许我声明ID列表(理想情况下,只有一个头文件,而不是在每一个“#FOR_EACH_WORD”指令,因为我没有在上面的例子)

  • 该解决方案必须不仅限于“创建一个从数据库表的对象”的构造。 有许多功能,其中大部分不是构造函数,需要这一点。

  • 的解决方案“让他们都值一个载体,然后运行‘为’跨越矢量循环”是一个明显的例子,并不能使用 - 这个代码在许多应用程序使用的库,成员是公众,并重新编写这些应用程序,而不是使用名称的成员矢量成员是出了问题的,可悲的。

Answer 1:

Boost.Preprocessor提出了许多方便的宏来执行这样的操作。 博扬已经雷斯尼克提供的解决方案使用这个库,但它假定每个成员名字的架构相同的方式。

既然你明确的要求possibily申报ID的列表,这里是一个应该更好地满足您的需求的解决方案。

#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宏。



Answer 2:

升压包括您可以用它来生成代码,这样一个伟大的预处理器库:

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


Answer 3:

你可以做这样的事情:创建一个适配器类或修改现有的类,以便指向这些领域的载体,有问题在类的构造函数添加所有成员变量的地址到载体,然后在需要时运行的换环上该矢量。 这样,你不(或几乎不)更改为外部用户类,并有一个很好的for循环的能力。



Answer 4:

当然,显而易见的问题是: 为什么你有100个成员的一类? 它并没有真正似乎清醒。

假设这是理智的还是-你看着升压预处理器库 ? 我从来没有用它自己(作为一个朋友曾经说:这样做会导致黑暗的一面),但是从我听到它应该是工作的工具。



Answer 5:

偷偷使用你自己的机器上的Perl来创建构造函数。 然后问,以增加你的薪水,因为你成功地维护的代码,这样一大块。



Answer 6:

你可以使用预处理程序定义成员,后来又使用相同的定义来访问它们:

#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

它的工作对我来说,却是很难调试。



Answer 7:

您也可以根据指针到成员实现访问者模式。 预处理器的解决方案后,这一个原来的方式更加debuggeable。

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


Answer 8:

为什么在运行时不去做呢? (我真的很讨厌宏两轮牛车)

你真正所要求的,在某种意义上,是类的元数据。

所以我会尝试这样的:

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: 重复整个多对多功能(> 20)这种行为同一性,当然每个函数的行为是不同的,所以没有办法因素的事情了。

你必须应用此相同的成语来构建功能机械

例如:setAllValuesTo(常量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;
    }
 }

如果你是一点点的创意与函数指针或模板函可以分解出变异操作,做几乎任何你想要到YourClass' AMember对集合的基础。 总结这些通用功能(可以采取功能性或函数指针)来实现您的当前设置在接口20种的公共方法。

如果你需要更多的元数据只是增加元数据映射的值域超出了成员指针。 (当然,异>第二以上会改变然后)

希望这可以帮助。



Answer 9:

你可以这样做他:

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

这并不完全符合你的描述,但最接近它可为您节省打字。



Answer 10:

大概是什么我想看看这样做是充分利用运行时多态性(动态分配)的。 让父类与不常用的东西的方法的成员。 成员来自父类的派生类。 需要不同的实现方法的那些实现自己的。 如果他们需要做太多的共同的东西,那里面的方法,他们可以向下转换成基类,并调用其方法的版本。

然后你有你的原班里面做的是调用每种方法的成员。



Answer 11:

我会建议一个小的命令行应用程序,写在任何一种语言,你或你的团队最精通英寸

某种模板语言添加到您的源文件。 对于这样的事情,你不需要实行全面的解析器或任何幻想这样的。 只要看看在一行的开头容易识别的字符,并且一些关键字来代替。

使用命令行应用程序的模板源文件转换成真正的源文件。 在大多数构建系统,这应该是很容易通过添加构建阶段,或者干脆告诉编译系统自动完成:“使用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 ++代码。

如果你曾经听过这句话“写入代码编写代码”,这是什么意思:)



Answer 12:

已经有很多很好的答案和想法在这里,但对多样性的缘故,我就提出了另一个。

在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)];
    }
}

,当然,对于其他任何功能上的所有成员工作,你会写类似的循环。

现在有这种技术的几个问题:

  • 对会员的操作使用运行时间循环,而不是其他的解决方案,这将产生操作的展开序列。
  • 此绝对取决于具有相同类型的每个成员上。 虽然这是由OP允许,人们仍然应该评估是否有可能在未来改变。 而其他的一些解决方案没有这个限制。
  • 如果我没有记错, offsetof只能定义为在POD类型由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实际上可以改变成别的东西,会返回一个参考成员。



文章来源: Is there a way to apply an action to N C++ class members in a loop over member names (probably via pre-processor)?