2011-02-08 90 views
7

是否有可能具有一个成员变量,这将能够计算指针从指针包含对象本身(在它的方法)?C++类的成员变量知道其自己的偏移

让我们对外调用接口包裹在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); 
}; 

,同样为0参数其他号码N.有关外方每个类,一个C++类被声明为一些特征,并且此模板使用这些特征(以及更多特征用于参数类型)来查找和调用外部方法。这可以用于像:

Foo foo; 
MethodProxy<Foo, barId, void()(int)> bar; 
bar(foo, 5); 

现在我想在做这样的方式被定义Foo什么,我可以调用,如:

Foo foo; 
foo.bar(5); 

没有多次重复签名。(显然创建静态部件和包装的方法中的呼叫是简单,右)。那么,事实上,这仍然很容易:

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

然而,这意味着对象将最终包含许多指向自己的指针副本。所以我在寻找一种方法,使这些实例能够计算从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; 
    ... 
}; 

行思但这抱怨说,foo是不完整的类型的点。

是的,我知道offsetof应该只为“POD”类型的工作,但实际上任何非虚成员,其中这将是工作。我也有类似的试图通过指针─(即)-member在这样的说法(使用虚拟基类),但这并不工作。

请注意,如果这样工作,它也可以用来实现委托给包含类的方法的C#类属性。

我知道如何用boost.preprocessor做上面提到的包装器方法,但参数列表必须以奇怪的形式指定。我知道如何编写宏以通过模板生成泛型包装,但这可能会导致较差的诊断。如果这些电话看起来像foo.bar()(5),这也是微不足道的。但我想知道一些聪明的把戏是否有可能(加上只有这样的巧招很可能是对的属性可用太)。

注意:成员类型不能实际专用于指向它的成员指针或它的偏移量,因为在指定偏移量之前必须知道类型。这是因为该类型可能会影响所需的对齐(考虑显式/特殊专业化)。

+0

,我读了好几次,但仍然没有得到你想要做什么,你想要一个通用的*属性*类是知道的它拥有什么 - 如果是这样,为什么它需要知道它拥有什么?我应该想象一个属性真正需要的是接受价值并返回价值的能力吗? – Nim 2011-02-08 19:07:52

+1

我也不明白。你想做什么? – mfontanini 2011-02-08 19:13:10

+0

@Nim:是的,我想要一个通用的“属性”类,它知道它拥有什么。对于财产,如果财产价值应该*计算*,则是必要的。在我的情况下,它是一个仿函数,它需要将指针传递给所有者到底层方法。 – 2011-02-08 19:47:57

回答

6

问一个问题是的最佳方式实现答案,所以这是我已经得到:

偏移量不能是模板参数,因为在计算偏移量之前必须知道类型。所以它必须由参数的函数返回。让我们添加一个标签类型(dummy struct),并将一个重载函数放入所有者或直接放入标签。这样我们就可以在一个地方定义我们需要的所有东西(使用宏)。下面的代码编译罚款与GCC 4.4.5和打印正确的指针所有成员:

#include <cstddef> 
#include <iostream> 

using namespace std; 

(只是序言使其真正编译)

template <typename Owner, typename Tag> 
struct offset_aware 
{ 
    Owner *owner() 
    { 
     return reinterpret_cast<Owner *>(
      reinterpret_cast<char *>(this) - Tag::offset()); 
    } 
}; 

这是为了使对象知道什么需要它是自己的偏移量。可以自由添加属性或仿函数或其他代码以使其有用。现在,我们需要声明一些额外的东西与该成员本身一起,所以让我们定义的宏:

#define OFFSET_AWARE(Owner, name) \ 
    struct name ## _tag { \ 
     static ptrdiff_t offset() { \ 
      return offsetof(Owner, name); \ 
     } \ 
    }; \ 
    offset_aware<Owner, name ## _tag> name 

这个结构定义的标签,并把在返回所需的偏移量的函数。比它定义数据成员本身。

请注意,成员需要按照此处定义的方式公开,但我们可以为标记支持受保护和私有属性轻松添加“朋友”声明。现在让我们使用它。

struct foo 
{ 
    int x; 
    OFFSET_AWARE(foo, a); 
    OFFSET_AWARE(foo, b); 
    OFFSET_AWARE(foo, c); 
    int y; 
}; 

很简单,不是吗?

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

这会在所有行上打印相同的指针值。 C++标准不允许成员大小为0,但它们只有实际内容的大小或1个字节,如果它们是空的,则与指针的4或8(取决于平台)字节相比较。

0

假设调用实际上需要对包含对象的引用,只需将引用存储到所有者。除非你有特定的内存分析证据表明它会导致显着的内存增加来存储额外的引用,否则只需以明显的方式进行。

+1

你有没有读过这个问题?这正是中间选项的作用,并且比我问是否有一种方法可以没有这个参考。 – 2011-02-08 19:44:26

1

现在,这里是一个MS-具体的解决方案,还在思考如何让它更普遍

#include <stdio.h> 

#define offs(s,m) (size_t)&(((s *)0)->m) 
#define Child(A,B,y) \ 
    __if_exists(X::y) { enum{ d_##y=offs(X,y) }; } \ 
    __if_not_exists(X::y) { enum{ d_##y=0 }; } \ 
    B<A,d_##y> y; 

template <class A, int x> 
struct B { 
    int z; 
    void f(void) { 
    printf("x=%i\n", x); 
    } 
}; 

template< class X > 
struct A { 
    int x0; 
    int x1; 
    Child(A,B,y); 
    Child(A,B,z); 
}; 

typedef A<int> A0; 

typedef A<A0> A1; 

int main(void) { 
    A1 a; 
    a.y.f(); 
    a.z.f(); 
} 
2

1)有这似乎配件的GCC扩展:

enum{ d_y = __builtin_choose_expr(N,offsetof(X,y),0) }; 

但预期它没有工作,即使手册说
“内置的功能并不计算不是表达选择“

2)成员指针似乎很有趣,例如。 offsetof可以这样定义:

template< class C, class T > 
int f(T C::*q) { 
    return (int)&((*(C*)0).*q); 
} 

但我仍然没有找到一个办法把它变成constexpr。

3)现在,这里的另一个版本:

#include <stdio.h> 

#pragma pack(1) 

template <class A, int x> 
struct B { 
    int z; 
    void f(void) { 
    printf("x=%i\n", x); 
    } 
}; 

#define STRUCT(A) template< int N=0 > struct A { 
#define CHILD(A, N, B, y) }; template<> struct A<N> : A<N-1> \ 
    { B<A<N>,sizeof(A<N-1>)> y; 
#define STREND }; 

STRUCT(A) 
    int x0; 
    int x1; 
    CHILD(A,1, B, y); 
    short x2; 
    CHILD(A,2, B, z); 
    char x3; 
STREND 

typedef A<2> A1; 

int main(void) { 
    A1 a; 
    a.y.f(); 
    a.z.f(); 
}