用于标记 GC 的 C++ 可变参数模板 "tuple"(指向变量的指针)

C++ variadic template "tuple" (of pointers to variables) for a marking GC

一个问题类似于 std::tuple given here 中的 for_each,但不完全相同。

直觉上,我想要一个由一系列变量组成的 "tuple" 指针,并且我想 "iterate" 在 "tuple"...

它是在编码天真的标记和精确扫描的上下文中garbage collector in C++14 (on Linux/Debian/Sid, either with GCC 4.9 or soon released 5.0 or with Clang/LLVM 3.5 或刚刚发布的 3.6)。

我不想使用 Boost。

我有两个 不相关的 class:PtrItemValuePtrItem class 是仅包含一个字段 Item* itemptr; 的 "pointer-like" class。 Value class 包含有区别的联合(参见 )。 classes PtrItemValue 都有一个方法

void scan_items(std::function<void(PtrItem)> f) const;

假设我有一个以

开头的块
{
  PtrItem pit1, pit2;
  Value valx, valy;

我想在同一块中紧接着写

 GCROOT(pit1,pit2,valx,valy);   /// at line 456

 class ProtoGcRoot {
   const char* gcrfile;
   int gcrline;
   ProtoGcRoot* gcrprev;
 protected:
   template <typename T1, typename... Args> class GcData
   {
        T1* ptr;
        GcData<Args...> rest;
   public:    
        GcData(T1& v, Args... args) : ptr(&v), rest(args...) {};
     ~GcData() { ptr = nullptr; };
     void scan_gc_data(std::function<void(PtrItem)> f) {
       if (ptr) ptr->scan_items(f);
       rest.scan_gc_data(f);
     }
   };
   template <> class GcData
   { GcData() {};
     ~GcData() {};
     void scan_gc_data(std::function<void(PtrItem)>) {};
   };
   ProtoGcRoot(const char*fil, int lin);
   ProtoGcRoot() = delete;
   ProtoGcRoot(const ProtoGcRoot&) = delete;
   ProtoGcRoot(ProtoGcRoot&&) = delete;
   ProtoGcRoot& operator = (const ProtoGcRoot&) = delete;
   virtual ~ProtoGcRoot();
 public:
   virtual void scan_gc_items (std::function<void(PtrItem)>)= 0;
 };             // end class ProtoGcRoot

 template<typename... Args>
 class GcRoot : ProtoGcRoot {
   ProtoGcRoot::GcData<Args...> gcdata;
 public:
   GcRoot(const char*fil, int lin, Args... rest)
     : ProtoGcRoot(fil,lin),  gcdata(rest...) {};
   ~GcRoot() {};
   virtual void scan_gc_items (std::function<void(PtrItem)> f) {
       gcdata.scan_gc_data(f);
     }
 };
 #define GCROOT_AT(Fil,Lin,...) GcRoot gc_root_##Lin{Fil,Lin,__VA_ARGS__}
 #define GCROOT(...) GCROOT_AT(__FILE__,__LINE__,__VA_ARGS__)

目的是让 gc_root_456 变量等价于

  void scan_gc_items (std::function<void(PtrItem)>f) {
     pit1.scan_items(f);
     pit2.scan_items(f);
     val1.scan_items(f);
     val2.scan_items(f);
  }

但我的代码无法编译:

 ./yacax.h:729:3: error: extraneous 'template<>' in declaration of class 'GcData'
   template <> class GcData
   ^
 ./yacax.h:729:21: error: redefinition of 'GcData' as different kind of symbol
   template <> class GcData
                     ^
 ./yacax.h:717:50: note: previous definition is here
   template <typename T1, typename... Args> class GcData
                                                  ^

GcData class 在 ProtoGcRoot 内部,因为我觉得不应该公开它。

我认为下面的代码可以提炼出相同的效果?与您的代码的唯一区别是无法重置指向 GC 声明项的指针..

#include <functional>
#include <iostream>
#include <set>


// Properly concatenate to form labels - useful when looking at -E output
#ifdef CONCAT_IMPL
#undef CONCAT_IMPL
#endif
#define CONCAT_IMPL( x, y ) x##y
#ifdef MACRO_CONCAT
#undef MACRO_CONCAT
#endif
#define MACRO_CONCAT( x, y ) CONCAT_IMPL( x, y )


// This is a model of your code, I would pass the function object here by reference
struct PtrItem
{
  void scan_items(std::function<void(PtrItem)>& f) const
  {
    std::cout << "scanning "  << this << std::endl;
    f(PtrItem{});
  }
};

struct Value
{
  void scan_items(std::function<void(PtrItem)>& f) const
  {
    std::cout << "scanning "  << this << std::endl;
    f(PtrItem{});
  }
};

class Root;

// Dumb global garbage collector
// because I can't rely on the destructor of the Root class anymore
// for demo
struct Gc
{
  static Gc& instance()
  {
    static Gc _i;
    return _i;
  }

  static void add(Root* inst)
  { instance().add_(inst); }
  static void remove(Root* inst)
  { instance().remove_(inst); }
  static void cleanup()
  { instance().cleanup_(); }

private:
  Gc() = default;

  void add_(Root* inst)
  { _s.insert(inst); }

  void remove_(Root* inst)
  { _s.erase(inst); }

  void cleanup_();

  std::set<Root*> _s;
};

// Base root
struct Root
{
    const char* file;
    int line;

    Root(const char* f, int i): file(f), line(i)
    {
      Gc::add(this); // register this scope
    }

    virtual ~Root()
    {
      Gc::remove(this); // de-register this scope
    }

    // Action
    virtual void scan(std::function<void(PtrItem)> f)
    { }
};

void
Gc::cleanup_()
{
  // Now cleanup - go through all registered scopes...
  auto f = [](PtrItem) { std::cout << "scanned" << std::endl; };
  for (auto r : _s)
   r->scan(f);
}

/**
 * To avoid the std::function<> construction, simply hold a reference directly to the lambda
 * @tparam Handler
 */
template <typename Handler>
struct ScopeRoot : public Root
{
  /** This is the lambda */
  Handler& handler;

  ScopeRoot(const char* f, int i, Handler& h): Root(f, i), handler(h)
  { }

  void scan(std::function<void(PtrItem)> f) override
  { handler(f); }
};

/**
 * This little wrapper allows us to piggy back on the operator, to
 * capture all the macro arguments!
 */
struct FWrapper
{
  /** Hold reference here to avoid copy */
  std::function<void(PtrItem)>& scanner;

  /**
   * Use the operator, to capture each registered variable
   * @param  GC-registered variable
   * @return this to allow chaining
   */
  template <typename T>
  FWrapper& operator,(T& v)
  {
    v.scan_items(scanner);
    return *this;
  }
};

/**
 * Now the macro is expanded to declare the lambda separately to allow us
 * to get it's type for the ScopeRoot instance!
 */
#define GCROOT_AT(Fil, Lin, ...)                                                          \
  auto MACRO_CONCAT(scope_gc_func, Lin) = [&](auto& f) { FWrapper{f}, __VA_ARGS__; }; ScopeRoot<decltype(MACRO_CONCAT(scope_gc_func, Lin))> MACRO_CONCAT(scope_gc_root, Lin){ Fil, Lin, MACRO_CONCAT(scope_gc_func, Lin) };

#define GCROOT(...) GCROOT_AT(__FILE__, __LINE__, __VA_ARGS__)

int main()
{
  PtrItem p1, p2;
  Value v1, v2;

  GCROOT(p1, p2, v1, v2)   
  // Trigger a scan
  Gc::cleanup();
}

基本上你保存你在 lambda 中需要的状态,而不是你拥有的递归模板结构。

真的很聪明(但在它的 初始 版本中没有得到很好的优化,即使 -O3 使用 g++-4.9;我猜改进后的版本效果更好)。但我最后写了类似的东西:

class GarbColl;

class PtrItem {
   Item* itemptr;
public:
   inline void mark_gc(GarbColl*) const;
   //// etc...
}; //end class PtrItem

class Value {
   ///... etc...
public:
   void scan_items(std::function<void(PtrItem)> f) const;
   inline void mark_gc(GarbColl*gc) const
   {
     scan_items([=](PtrItem pit)
               {pit.mark_gc(gc);});
   };
}; /// end class Value

class ProtoGcRoot { 
  // ...etc....
protected:
  ProtoGcRoot(const char*fil, int lin);
  virtual ~ProtoGcRoot();
public:
  virtual void scan_gc_items (GarbColl*)= 0;
}; /// end class ProtoGcRoot

template <typename... Args> struct GcData;

template <> struct GcData<> {
  GcData() {};
  void mark_gc(GarbColl*) {};
};
template <class Arg1Class, typename... RestArgs>
struct GcData<Arg1Class,RestArgs...>
{
 Arg1Class* argdata;
 GcData<RestArgs...> restdata;
 GcData(Arg1Class& arg, RestArgs... rest)
   : argdata(&arg), restdata(rest...) {};
 void mark_gc(GarbColl*gc)
 {
   if (argdata) argdata->mark_gc(gc);
   restdata.mark_gc(gc);
 };
};

template<typename... Args>
class GcRoots : public ProtoGcRoot
{
  GcData<Args...> gcdata;
public:
  GcRoots(const char*fil, int lin, Args... rest...)
    : ProtoGcRoot(fil,lin), gcdata(rest...) {};
  ~GcRoots() {};
  virtual void scan_gc_items (GarbColl* gc)
  {
    this->gcdata.mark_gc(gc);
  }
};
#define GC_ROOTS_HERE_AT_BIS(Fil,Lin,...)  \
     gc_roots_##Lin(Fil,Lin,__VA_ARGS__)
#define GC_ROOTS_HERE_AT(Fil,Lin,...) \
     GC_ROOTS_HERE_AT_BIS(Fil,Lin,__VA_ARGS__)
#define GC_ROOTS_HERE(...) \
     GC_ROOTS_HERE_AT(__FILE__,__LINE__,__VA_ARGS__)
#define GC_ROOTS2_BIS(Fil,Lin,R1,R2) \
   GcRoots<decltype(R1),decltype(R2)> gc_roots2_##Lin(Fil,Lin,R1,R2)
#define GC_ROOTS2_AT(Fil,Lin,R1,R2) GC_ROOTS2_BIS(Fil,Lin,R1,R2)
#define GC_ROOTS2(R1,R2) GC_ROOTS2_AT(__FILE__,__LINE__,R1,R2)

然后我可以编码

 void foo(PtrItem pit, Value v) { 
    GcRoots<PtrItem,Value> GC_ROOTS_HERE(pit,v);
 }

 void foo(PtrItem pit, Value v) {
    GC_ROOTS2(pit,v);
 }

有点遗憾我无法定义可变参数和多类型宏 GC_ROOTS 但我可以接受多个宏 GC_ROOTS1 ... GC_ROOTS15

C++ 中的元编程工具真是令人头疼。我更喜欢 Common Lisp 宏,甚至 MELT 宏。