Переменная-член класса C ++, знающая свое собственное смещение - PullRequest
11 голосов
/ 08 февраля 2011

Возможно ли иметь переменную-член, которая могла бы вычислять указатель на содержащий объект из указателя на себя (в его методе)?

Давайте создадим интерфейс внешнего вызова в 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 ]

8 голосов
/ 08 февраля 2011

Задать вопрос - лучший способ осознать ответ, так что вот где я получил:

Смещение не может быть аргументом шаблона, потому что тип должен быть известен до того, как смещение может быть вычислено. Поэтому он должен быть возвращен функцией аргумента. Давайте добавим тип тега (фиктивная структура) и либо поместим перегруженную функцию в 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, но они будут иметь размер своего фактического содержимого или 1 байт, если они в противном случае пусты по сравнению с 4 или 8 (в зависимости от платформы) байтами для указателя.

2 голосов
/ 09 февраля 2011

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();
}
1 голос
/ 08 февраля 2011

Пока вот одно решение для 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();
}
0 голосов
/ 08 февраля 2011

Предполагая, что вызовам действительно нужна ссылка на содержащий объект, просто сохраните ссылку на владельца. Если у вас нет конкретных доказательств профилирования памяти, которые приводят к значительному увеличению памяти для хранения дополнительных ссылок, просто сделайте это очевидным способом.

...