2011-02-08 89 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(); 
}