13

是否有可能有一个成员变量,它能够从指向自身的指针(在它的方法中)计算指向包含对象的指针?

让我们在 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”类型,但实际上适用于任何非虚拟成员,这将是有效的。我同样尝试在该参数中传递指向(那个)成员的指针(使用虚拟基类),但这也不起作用。

请注意,如果这可行,它还可以用于实现类似 C# 的属性,委托给包含类的方法。

我知道如何使用 boost.preprocessor 执行上述包装方法,但参数列表必须以一种奇怪的形式指定。我知道如何编写宏以通过模板生成通用包装器,但这可能会导致诊断不佳。如果电话看起来像foo.bar()(5). 但我想知道一些聪明的技巧是否可能(加上只有这样的聪明技巧可能也可用于属性)。

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

4

4 回答 4

9

提出问题是实现答案的最佳方式,所以这就是我得到的地方:

偏移量不能是模板参数,因为在计算偏移量之前必须知道类型。所以它必须由参数的函数返回。让我们添加一个标签类型(虚拟结构),然后将重载函数放入 Owner 或直接放入标签中。这样我们就可以在一个地方定义我们需要的一切(使用宏)。以下代码可以使用 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,但与指针的 4 或 8(取决于平台)字节相比,如果它们为空,则它们只会具有实际内容的大小或 1 字节。

于 2011-02-08T20:50:22.450 回答
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();
}
于 2011-02-08T22:45:05.963 回答
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();
}
于 2011-02-08T20:00:07.907 回答
0

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

于 2011-02-08T19:16:33.407 回答