Jan*_*dec 11 c++ templates properties offsetof
是否有可能有一个成员变量,它能够从指向自身的指针计算指向包含对象的指针(在它的方法中)?
让我们在API中包含一个外部调用接口,如下所示:
template <typename Class, MethodId Id, typename Signature>
class MethodProxy;
template <typename Class, MethodId Id, typename ReturnT, typename Arg1T>
class MethodProxy<Class, Id, ReturnT ()(Arg1T) {
public:
ReturnT operator()(Class &invocant, Arg1T arg1);
};
Run Code Online (Sandbox Code Playgroud)
类似地,对于从0到N的其他数量的参数.对于外来的每个类,一个C++类声明具有一些特征,并且该模板使用这些特征(以及参数类型的更多特征)来查找和调用外部方法.这可以像:
Foo foo;
MethodProxy<Foo, barId, void ()(int)> bar;
bar(foo, 5);
Run Code Online (Sandbox Code Playgroud)
现在我想做的是以Foo这种方式定义,我可以这样称呼:
Foo foo;
foo.bar(5);
Run Code Online (Sandbox Code Playgroud)
不重复签名多次.(显然创建一个静态成员并在方法中包装调用很简单,对吧).嗯,事实上,这仍然很容易:
template <typename Class, MethodId Id, typename Signature>
class MethodMember;
template <typename Class, MethodId Id, typename ReturnT, typename Arg1T>
class MethodMember<Class, Id, ReturnT ()(Arg1T) {
MethodProxy<Class, Id, Signature> method;
Class &owner;
public:
MethodMember(Class &owner) : owner(owner) {}
ReturnT operator()(Arg1T arg1) { return method(owner, arg1); }
};
Run Code Online (Sandbox Code Playgroud)
然而,这意味着该对象最终将包含许多指向自身的指针副本.所以我正在寻找一种方法,使这些实例能够计算所有者指针this和一些额外的模板参数.
我正在思考
template <typename Class, size_t Offset, ...>
class Member {
Class *owner() {
return reinterpret_cast<Class *>(
reinterpret_cast<char *>(this) - Offset);
}
...
};
class Foo {
Member<Foo, offsetof(Foo, member), ...> member;
...
};
Run Code Online (Sandbox Code Playgroud)
但是这抱怨Foo在这一点上是不完整的类型.
是的,我知道offsetof应该只适用于"POD"类型,但实际上对于任何非虚拟成员,这将是有效的.我已经尝试过在该参数中传递指向 - ( - ) - 成员(使用虚拟基类),但这也不起作用.
请注意,如果这样做,它也可以用于实现委托给包含类的方法的类似C#的属性.
我知道如何使用boost.preprocessor来执行上面提到的包装器方法,但是参数列表必须以奇怪的形式指定.我知道如何编写宏来通过模板生成通用包装器,但这可能会导致很差的诊断.如果电话看起来像这样也是微不足道的foo.bar()(5).但是我想知道是否有一些聪明的技巧是可能的(加上只有这样聪明的技巧才可能用于属性).
注意:成员类型实际上不能专门指向它的成员指针或它的偏移量,因为在分配该偏移量之前必须知道该类型.那是因为类型会影响所需的对齐(考虑显式/ parcial特化).
提出问题是实现答案的最佳方式,所以这就是我所拥有的:
偏移量不能是模板参数,因为必须先知道类型才能计算偏移量.所以它必须由参数的函数返回.让我们添加一个标签类型(虚拟结构),并将一个重载函数放入Owner或直接放入标签.这样我们就可以在一个地方定义我们需要的一切(使用宏).以下代码使用gcc 4.4.5编译好并为所有成员打印正确的指针:
#include <cstddef>
#include <iostream>
using namespace std;
Run Code Online (Sandbox Code Playgroud)
(只是序言让它真正编译)
template <typename Owner, typename Tag>
struct offset_aware
{
Owner *owner()
{
return reinterpret_cast<Owner *>(
reinterpret_cast<char *>(this) - Tag::offset());
}
};
Run Code Online (Sandbox Code Playgroud)
这就是让对象意识到它自己的偏移量所需要的.可以自由添加属性或仿函数或其他代码以使其有用.现在我们需要声明一些额外的东西以及成员本身,所以让我们定义这个宏:
#define OFFSET_AWARE(Owner, name) \
struct name ## _tag { \
static ptrdiff_t offset() { \
return offsetof(Owner, name); \
} \
}; \
offset_aware<Owner, name ## _tag> name
Run Code Online (Sandbox Code Playgroud)
这将结构定义为标记,并放入一个返回所需偏移量的函数.比它定义数据成员本身.
请注意,该成员需要公开,如此处所定义,但我们可以轻松地为标记支持受保护和私有属性添加"朋友"声明.现在让我们使用它.
struct foo
{
int x;
OFFSET_AWARE(foo, a);
OFFSET_AWARE(foo, b);
OFFSET_AWARE(foo, c);
int y;
};
Run Code Online (Sandbox Code Playgroud)
简单,不是吗?
int main()
{
foo f;
cout << "foo f = " << &f << endl
<< "f.a: owner = " << f.a.owner() << endl
<< "f.b: owner = " << f.b.owner() << endl
<< "f.c: owner = " << f.c.owner() << endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这会在所有行上打印相同的指针值.C++标准不允许成员具有0大小,但是它们只有实际内容的大小或1个字节(如果它们是空的),而指针则是4或8(取决于平台)字节.