简体   繁体   中英

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

Suppose I have an object with many members:

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

Is there a short/concise way to do something like:

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

Instead of accessing each one individually?

I think I could put them in a vector and use a shared_ptr for the same effect, I was just wondering if say, Boost or some other popular library doesn't have something to do this automatically.

C++ does not support class introspection, so you cannot iterate over all of the members in a class like that - not without having a (manually written) function that iterates over all members for you anyway.

You could, in principle, add a template member like so:

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

That said, I would regard this as a broken design; you've gone and exposed all of your internal members publicly. What happens if you add one later? Or change the semantics of one? Make one a cached value that's sometimes out of date? etc. Moreover, what happens if you have members which don't all inherit from the same types?

Step back and reconsider your design.

There are several solutions to this issue, contrary to what the naysayers blabber, but no built-in way.

C++ support a limited kind of introspection, at compile-time: you can check the template parameters.

Using either Boost.Tuple or Boost.Fusion (for its map), you can indeed achieve what you wish. In Boost.Fusion you even have BOOST_FUSION_ADAPT_STRUCT to transform a basic structure into a Fusion Sequence (and thus iterate it).

It requires quite a bit of template meta-programming though.

C++ can do something like this, if you play by its rules and use template metaprogramming.

Instead of storing your stuff in a struct or class, store it in a tuple:

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

Then you can use template metaprogramming algorithms and so forth (see Boost.Fusion) to access members and poke at stuff. You can iterate, template-style, over the elements of the tuple.

This is my way to do what you want to achieve in C++11 . It uses tuple and templates . It's not short and concise but if you wrap somecode in a header file is acceptable. There is my full compilable example:

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

It is possible that what you're looking for here is the Visitor pattern. If you have an object as you describe, with a number of non-trivial member fields, and you find that you have a number of different functions that all traverse this data structure in the same way, the visitor pattern can be very helpful in reducing the amount of code duplication. It is not automatic, through, you have to write the functions that traverse all the member fields, but you only have to do that once, and you can use it many times over with different visitor classes that do different things.

The visitor pattern does involve writing quite a bit of code, you need an abstract base class for the visitors:

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 ...
};

Then you need accept functions in all the classes that are going to be visited:

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

And finally you need to implement concrete visitor classes that do the actual work you're interested in, which generally amounts to gather information from the data structure, making changes to the data structure, or combinations of both. Google Visitor pattern and you'll find lots of help on this.

This is not possible with C++, if you really Really REALLY need this then instead use C#. But I seriously doubt you do.

Having said that, about the only option you really have is to use .NET and use managed C++ which microsoft calls Managed C++/CLI. But the caveat, is your class must be a managed 'ref class', meaning a managed class. Eventually it all gets compiled down to MSIL, and intermediate language that is language agnostic. That's the only way you could then use .NET reflection on it at runtime to discover it's member functions and values. However even using .NET it's not as easy as you described how you would like to use it above. Another downside to reflection is that it's slow, so if you are using it heavily you have a bad design. Reflection is nice in that .NET uses it to help in serializing data types, which makes XML serialization very easy to use.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM