用于标记 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:PtrItem
和 Value
。 PtrItem
class 是仅包含一个字段 Item* itemptr;
的 "pointer-like" class。 Value
class 包含有区别的联合(参见 )。 classes PtrItem
和 Value
都有一个方法
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 宏。
一个问题类似于 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:PtrItem
和 Value
。 PtrItem
class 是仅包含一个字段 Item* itemptr;
的 "pointer-like" class。 Value
class 包含有区别的联合(参见 PtrItem
和 Value
都有一个方法
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 宏。