2011-02-08 7 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», но на практике для любого не виртуального элемента, который это будет, работает. Аналогичным образом я попытался передать в этом аргументе указатель-to-the (-member) (используя фиктивный базовый класс), но это тоже не работает.

Обратите внимание, что если это сработало, оно также может быть использовано для реализации свойств, связанных с C#, передающих методы содержащего класс.

Я знаю, как использовать описанные выше методы обертки с boost.preprocessor, но списки аргументов должны быть указаны в странной форме. Я знаю, как писать макрос для создания общих оболочек с помощью шаблонов, но это, вероятно, даст плохую диагностику. Было бы также тривиально, если бы вызовы могли выглядеть как foo.bar()(5). Но я хотел бы знать, возможно ли умный трюк (плюс только такой умный трюк, вероятно, будет полезен и для свойств).

Примечание. Тип элемента не может быть фактически специализирован ни на указателе участника, ни на его смещении, поскольку тип должен быть известен до того, как это смещение может быть назначено. Это потому, что тип может повлиять на требуемое выравнивание (рассмотрим явную/парциальную специализацию).

+0

Я читал, что в несколько раз, но до сих пор не понимаю, что вы хотите сделать, вы хотите родовое * свойство * класс, который знает о что ему принадлежит - если да, зачем ему нужно знать, что ему принадлежит? Я должен представить себе, что все необходимое свойство - это способность принимать значение и возвращать значение? – Nim

+1

Я тоже этого не понимаю.Что ты пытаешься сделать? – mfontanini

+0

@Nim: Да, мне нужен общий класс свойств, который знает, что ему принадлежит. Для свойства необходимо, если значение свойства должно быть * вычислено *. В моем случае, однако, функтор должен передать указатель владельцу на основной метод. –

ответ

6

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

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

+0

Вы должны, вероятно, заметить, что если кто-то наследует полиморфный класс от 'foo', это решение сломается. –

+0

@MarkB: Будет ли? Независимо от того, из чего происходит 'foo', член все еще находится на том же самом смещении от суффикса' foo', поэтому 'owner()' возвращает указатель на это субинстентство, которое именно там, где ожидается 'foo *' точка. –

+0

@JanHudec, это блестящее событие спустя полвека. Благодарю. – Kumputer

0

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

+1

У вас есть вопрос? Это именно то, что делает средний вариант, и я спросил, есть ли способ получить без этой ссылки. –

+1

@Jan Hudec Почему, по вашему мнению, дополнительные ссылки - настоящая проблема, которую нужно исправить? Мое первое чтение вопроса заключается в том, что здесь не может быть реальной проблемы. –

+2

Ну, вопрос конкретно требует решения, которое их не имеет. Это не вопрос, есть ли у них проблемы или нет. — Я специально исключил их, потому что в противном случае вопрос тривиален и не стоит обсуждать. –

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(); 
} 
+0

__if_exists/__ if_not_exists не требуется. Это ошибка, если она не существует. И это единственная вещь, специфичная для MS, не так ли? –

+0

Я немного подкорректировал (в основном заменяя 'offs' на' offsetof' от '', но gcc отказывается его компилировать, потому что он не позволяет ссылаться на членов до их объявления (кроме тел метода). C++ должен разрезать некоторые углы, потому что требуемое выравнивание в общем случае может зависеть от аргумента шаблона. –

+0

Идея состоит в том, что класс определяется впервые без смещения, затем определяется снова с использованием смещений из первого экземпляра. Его легко сделать с например, 2x #include + переопределение макросов, но это неудобно. Тем не менее, я до сих пор не знаю, как настроить SFINAE для . Неэксистантные члены класса (переопределение оператора-> или -> * показалось перспективным, , но не сработало) Таким образом, он не будет работать без __if_exists. – Shelwien

2

1) Там расширение НКИ, которое казалось примеркой:

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(); 
} 
+0

Выражение, которое не выбрано, не имеет значения. Элемент всегда существует, и необходимо выбрать смещенную ветвь. Он не может быть оценен во времени - необходимо использовать (встроенную) функцию. –

+0

До сих пор нет доказательств того, что чистый способ компиляции не существует (фактически я уже разместил 2). – Shelwien

Смежные вопросы