C++:遍历对象的所有成员?

C++: Iterating through all of an object's members?

本文关键字:成员 遍历 对象 C++      更新时间:2023-10-16

假设我有一个有许多成员的对象:

class Example {
    AnotherClass member1;
    AnotherClass member2;
    YetAnotherClass member3;
    ...
};

有没有一种更简洁的方式来做某事,比如:

foreach(member m: myExample)
    m.sharedMethod();

而不是单独访问每个?

我想我可以把它们放在一个矢量中,并使用shared_ptr来达到同样的效果,我只是想知道是否说,Boost或其他流行的库没有自动做到这一点。

c++不支持类自省,所以你不能像那样遍历类中的所有成员——除非有一个(手动编写的)函数为你遍历所有成员。

原则上,您可以像这样添加模板成员:

template<typename Functor>
void doAllMembers(Functor &f) {
  f(member1);
  f(member2);
  f(member3);
}

也就是说,我会认为这是一个失败的设计;你把你所有的内部成员都公开了。如果稍后再加一个会怎么样?或者改变一个的语义?使一个缓存值有时是过时的?等。此外,如果成员并非全部继承自相同类型,会发生什么情况?

这个问题有几个解决方案,与反对者的喋喋不休相反,但没有内置的方法。

c++在编译时支持一种有限的自省:你可以检查模板的形参。

使用Boost.TupleBoost.Fusion(用于其地图),您确实可以实现您的愿望。在提高。你甚至有BOOST_FUSION_ADAPT_STRUCT将基本结构转换为融合序列(从而迭代它)。

它需要相当多的模板元编程

c++可以做类似的事情,如果你遵守它的规则并使用模板元编程。

不要将数据存储在结构体或类中,而是将其存储在元组中:

typedef boost::tuple<AnotherClass, AnotherClass, YetAnotherClass> Example;

然后你可以使用模板元编程算法等等(参见Boost.Fusion)来访问成员并戳东西。您可以以模板样式遍历元组中的元素。

您在这里寻找的可能是Visitor模式。如果您有一个对象,如您所描述的,具有许多重要的成员字段,并且您发现您有许多不同的函数,它们都以相同的方式遍历这个数据结构,那么访问者模式可以非常有助于减少代码重复的数量。这不是自动的,因为你必须编写遍历所有成员字段的函数,但你只需要做一次,你可以在做不同事情的不同访问者类中多次使用它。

访问者模式确实涉及编写相当多的代码,您需要为访问者提供一个抽象基类:

class VisitorBase 
{
    virtual void enter(Example& e)=0;
    virtual void leave(Example& e)=0;
    virtual void enter(AnotherClass& e)=0;
    virtual void leave(AnotherClass& e)=0;
    etc ...
};

那么你需要接受所有将要访问的类中的函数:

void Example::accept( VisitorBase& visitor ) 
{
    visitor.enter(*this);
    member1.accept(visitor);
    member2.accept(visitor);
    member3.accept(visitor);
    visitor.leave(*this);
}

最后,您需要实现具体的访问者类来完成您感兴趣的实际工作,这通常相当于从数据结构中收集信息,对数据结构进行更改,或者两者的组合。谷歌访问者模式,你会在这方面找到很多帮助。

这是我在 c++ 11中实现的方法。它使用元组模板。它并不简短,但如果您将一些代码包装在头文件中是可以接受的。这是我完整的可编译示例:

#include <iostream>
#include <string>
using namespace std;
#include <tuple>
//Our iteratation code
//first a iteration helper structure
template<int N = 0>
struct IterateP {
  template<class T>
  typename std::enable_if<(N < std::tuple_size<T>::value), void>::type
  iterate(T& t) {
    std::get<N>(t).sharedMethod(); //there is the method name
    IterateP<N+1>().iterate<T>(t);
  }
  template<class T>
  typename std::enable_if<!(N < std::tuple_size<T>::value), void>::type
    iterate(T&) {}
};
//wrapper of the helper structure for a more comfortable usage
template <typename T>
void iterate(T& t) { IterateP<>().iterate(t.members); } //look at the .members, is the name of the class tuple
//Helper notation macro. 
#define MEMBER(name, i) std::tuple_element<i,decltype(members)>::type &name = std::get<i>(members)
//YOUR CLASSES
struct AnotherClass {
  int value;
  void sharedMethod() { cout << value << endl; }
};
struct YetAnotherClass {
  string value;
  void sharedMethod() { cout << value << endl; }
};

//The class with iterable members
struct Example {
  std::tuple<AnotherClass, AnotherClass, YetAnotherClass> members; //members must be in a tuple
  //These are helper member definition to access the tuple elements as normal members (instance.member1)
  //If you don't you this you need to access the members with tuple classes
  MEMBER(member1, 0); //first param is the member name and the second is it's position in the tuple.
  MEMBER(member2, 1);
  MEMBER(member3, 2);
};
//USAGE
int main() {
  Example example;
  //setting members just as a normal class 
  example.member1.value = 1;
  example.member2.value = 2;
  example.member3.value = "hola";
  //magic
  iterate(example);
}

这在c++中是不可能的,如果你真的真的真的需要这个,那就用c#吧。但我很怀疑你是否知道。

说到这里,你唯一的选择就是使用。net和managed c++,微软称之为managed c++/CLI。但是需要注意的是,你的类必须是一个托管的ref类,意思是一个托管类。最终,所有这些都被编译成MSIL,以及与语言无关的中间语言。这是在运行时使用。net反射来发现它的成员函数和值的唯一方法。然而,即使使用。net也不像你上面描述的那样简单。反射的另一个缺点是它很慢,所以如果你大量使用它,你的设计就会很糟糕。反射很好,因为. net使用它来帮助序列化数据类型,这使得XML序列化非常容易使用。