Interfejsy w C++ i zajmowana pamięć

0

Czesć,
W swoim programi mam klasę Triangle. Wygodnie mi jest, żeby implementowała ona dwa interfejsy: IRenderable oraz IShape. Interfejs, to po prostu zwykła struktura, ale bez żadnych danych i każda jej metoda jest czysto wirtualna. Poniżej uproszczona wersja kodu:

struct IShape
{
	virtual bool Intersects(Ray r)=0;
};

struct IRenderable
{
	virtual Color GetColor(Vector3 p)=0;
};

struct Triangle:public IRenderable,public IShape
{
	Vector3 v1,v2,v3; //3*12=36 bytes
	
	virtual bool Intersects(Ray r) {...}

	virtual Color GetColor(Vector3 p) {...}
};

Obiektów klasy Triangle mam w jednym czasie bardzo dużo (rzedu kilkuset tysięcy), więc chciałbym, żeby zajmowały jak najmniej miejsca w pamięci. Sądziłem, że jeśli IShape i IRenderable są interfejsami i nie mają żadnych pól, to w obiektach klasy Triangle, nie są potrzebne dwa wskaźniki na vtable i wystarczy tylko jeden. Gdzieś o tym kiedyś czytałem. Niestety okazało się, że poniższy kod:

Triangle* triangle=new Triangle();
cout<<"sizeof(*triangle) = \t"<<sizeof(*triangle)<<endl;

wypisuje u mnie 44. 36 bajtów zajmują pola. Wchodzi na to, że pozostałe 8 bajtów zajmują 2 wskaźniki na vtable.
Nie wiem, może mi się po prostu wydawało, że wystarczy 1 wskaźnik. jeśli tak, to do czego potrzebne są oba?
Sprawdzałem to na MSVC++ 2008.

0

http://en.wikipedia.org/wiki/Virtual_method_table chyba za dużo nie da się z tym zrobić.

0

Ładne to może nie jest, ale jeśli Ci zależy na oszczędności pamięci a przy okazji chcesz mieć obiektowy kod - mój pomysł:

#include <iostream>

#pragma pack(1)
struct IA
{
    virtual void foo() = 0;
};

struct IB
{
    virtual void bar() = 0;
};

// pierwszy hack - interfejs zbierający wszytko
struct IAB
{
    virtual void foo() = 0;
    virtual void bar() = 0;
};

// drugi hack - wrappery. Dla wygody można by zrobić jakąś ładną konwersję
// IAB->IA_Wrap i IAB->IB_Wrap
// opekowywanie powinno być zoptymalizowane przez kompilator.
struct IA_Wrap : IA
{
   IAB &ab;
   virtual void foo() { ab.foo(); }
};

struct IB_Wrap : IB
{
   IAB &ab;
   virtual void bar() { ab.bar(); }
};

// dziedziczymy tylko po jednym interfejsie
struct C : public IAB
{
    void foo() { }
    void bar() { }
};
#pragma pack(0)

int main()
{
	std::cout << sizeof(C);
}
0

Interfejsy w szablonach mogły by być oszczędniejsze.

Tu jest coś co może naprowadzić na trop:
http://accu.org/index.php/journals/434

Miałem gdzieś coś bardziej pomocnego, dokleję jak znajdę...

0
Zjarek napisał(a)

http://en.wikipedia.org/wiki/Virtual_method_table chyba za dużo nie da się z tym zrobić.

W Wiki właśnie jest napisane, że wszystko zależy od implementacji. To, że zwykle kompilatory ładują wiele wskaźników przy wielodziedziczeniu nie oznacza, że nie dałoby się załatwić sprawy jednym. Jest jakaś bardzo mała szansa, że da się to zmienić jakąś opcją w kompilatorze.

0

Zgodnie z tym:

http://www.cdiggins.com/bil.html

na każdą vtable stracisz 4 bajty, chyba że zastosujesz powyższe rozwiązanie (Boost Interface Library) - wtedy tylko 8 bajtów niezależnie od liczby implementowanych interfejsów.
Ale na ile to działa a na ile nie to już kwestia do przetestowania.

0

na każdą vtable stracisz 4 bajty

Taki schemat jest zwykle stosowany w C++, bo w C++ nie ma dynamicznej dewirtualizacji i deoptymalizacji jak np w JVM. W JVM spokojnie wystarcza jeden wskaźnik do vtable i wszystko spoko działa szybko. W C++ nie ma JIT, więc rozwiązanie z jednym wskaźnikiem do vtable pewnie byłoby wolne, ale i tak oszczędzałoby pamięć.

0

@Tezcatlipoca: Przyznam, że nie bardzo rozumiem. W jaki sposób odbywa się tu rzutowanie z klasy C na IA albo IB?
@vpiotr: Rozwiązanie z boosta widziałem już wcześniej, ale wygląda ono tak paskudnie, że chyba nie jest warte zachodu w moim przypadku:).
Rozrysowałem sobie dokładnie cały mechanizm wielokrotnego dziedziczenia po interfejsach z jednym wskaźnikiem na vtable i doszedłem do wniosku, że nie jest to takie proste jak mi się wydawało. Ale i tak chyba mniej magiczne jak zwykłe wielokrotne dziedziczenie po klasach z danymi.
W każdym razie dzięki za odpowiedzi. Paru nowych rzeczy zawsze się człowiek nauczy:).

1

Na pewno lepiej stosować interfejsy niż wielobazowość. Jak się czyta o problemach związanych z tym drugim to włos się na głowie jeży...

Java i C# nie mają wielobazowości - pewnie jakieś powody ku temu mieli.
http://en.wikipedia.org/wiki/Multiple_inheritance

Ja do interfejsów korzystam z klas abstrakcyjnych

2

Źle podchodzisz do tematu. Po co pakować każdy trójkąt w taką klasę z interesami. Zajętość pamięci to mały problem, większym problemem będzie kosztowność obliczeń - dereferencje, wywołania metod, to wszystko kosztuje. Powinieneś raczej operować na zbiorach trójkątów niż na pojedynczych trójkątach.

1 użytkowników online, w tym zalogowanych: 0, gości: 1