2016-03-22 13 views
25

Jest to nawiązanie do innej kwestii kopalni: What is the optimal order of members in a class?Czy publiczne i prywatne mają wpływ na układ pamięci obiektu?

nie zmienia niczego (oprócz widoczności) gdybym organizować członków w taki sposób, że biorą publiczne, chronione i prywatne okazuje?

class Example 
{ 
public: 
    SomeClass m_sc; 
protected: 
    char m_ac[32];  
    SomeClass * m_scp; 
private: 
    char * m_name; 
public: 
    int m_i1; 
    int m_i2; 
    bool m_b1; 
    bool m_b2; 
private: 
    bool m_b3; 
}; 

Czy istnieje różnica między tą klasą a klasą, w której wszystkie osoby publikują w czasie wykonywania? Chcę przestrzegać zasady zamawiania typów z dużych na małe (jeśli czytelność nie jest poważnie skrzywdzona).

Zakładam, że w ogóle nie ma wpływu na skompilowany program, tak jak const jest sprawdzany tylko podczas kompilacji. Czy to jest poprawne?

+0

Unikaj 'chronionych', unikaj nagich wskazówek. –

+0

Praktycznie duplikat: http://stackoverflow.com/q/15763091/560648 Proszę wyszukać przed pytaniem. –

Odpowiedz

32

Odpowiedź zależy od wersji językowej, ponieważ zmieniła się z C++ 03 na C++ 11.

w C++ 03, zasada brzmiała:

Członkowie wewnątrz bloku sterującego sam dostęp (czyli z jednego public, protected, private słowo aby następnego z tego zestawu) mają być przydzielane w kolejności deklaracji w klasie, niekoniecznie w sposób ciągły.

w C++ 11, zasada ta została zmieniona na:

Użytkownicy z poziomu sterowania sam dostęp (publiczne, chronione, prywatne) są przydzielane w kolejności deklaracji wewnątrz klasy, niekoniecznie w sposób ciągły.

Więc w C++ 03, można zagwarantować (używam @ oznaczać przesunięcie członka w klasie):

  • @m_ac < @m_scp
  • @m_i1 < @m_i2 < @m_b1 < @m_b2

W C++ 11 masz kilka dodatkowych gwarancji:

  • @m_ac < @m_scp
  • @m_sc < @m_i1 < @m_i2 < @m_b1 < @m_b2
  • @m_name < @m_b3

W obu wersjach, kompilator może zmienić kolejność członków w różnych sieciach, jak uzna to za stosowne, a nawet może przeplatać łańcuchy.


Należy pamiętać, że istnieje jeszcze jeden mechanizm, który może wejść do obrazu: zajęcia standardowego układu.

Klasa jest układem standardowym, jeśli nie ma wirtualnych, jeśli wszystkie niestatyczne elementy danych mają tę samą kontrolę dostępu, nie ma klas podstawowych ani niestatycznych elementów danych o niestandardowym typie układu lub typ odniesienia i jeśli ma najwyżej jedną klasę z niestatycznymi elementami danych w łańcuchu dziedziczenia (npnie może zarówno zdefiniować własnych niestatycznych elementów danych, jak i odziedziczyć niektórych z klasy bazowej).

Jeśli klasa jest standardowym układ, istnieje dodatkowa gwarancja, że ​​adres jego pierwszej niestatyczny członka danych jest identyczna z samego obiektu klasy (co oznacza po prostu, że wyściółka nie może występować na początku układ klasy).

Należy zauważyć, że warunki będące standardowym układem, a także praktyczne kompilatory, które nie dokonują pesymizacji, skutecznie oznaczają, że w klasie układu standardowego elementy będą układane w sposób ciągły według kolejności deklaracji (z dopełnieniem do wyrównania w razie potrzeby przeplatanym)).

+0

Czy masz jakieś dane na temat tego, w jaki sposób kompilatory zwykle porządkują członków w praktyce? –

+0

@JanDvorak Nie, przepraszam. Nie programuję systemów, w których porządek danych byłby tak ważny, więc nigdy nie miałem powodu, aby to sprawdzić. Powiedziałbym, że dokumenty ABI platformy i dokumenty kompilatora mogą być dobrym punktem wyjścia do odkrycia. – Angew

4

Twierdzę, że zasada zamawiania nie zawsze jest najlepsza. Jedyne, co możesz zrobić, to unikanie "dopełnienia".

Jednak kolejną "regułą" do naśladowania jest posiadanie najbardziej "gorących" elementów na górze, tak aby mogły zmieścić się w linii pamięci podręcznej, która zwykle wynosi 64 bajty.

Wyobraź sobie, że masz pętlę, która sprawdza flagę twojej klasy, a jej przesunięcie wynosi 1, a twój drugi jest przesunięty o 65, a drugi o przesunięcie 200. Otrzymasz pomyłki w pamięci podręcznej.

int count = 0; 

for (int i = 0; i < 10; i++) 
{ 
    if (class->flag/*offset:1*/ == true && class->flag2 == true/*offset:65*/) 
      count += class->n; /*offset: 200*/    
} 

że pętla będzie dużo bardziej wolniej niż cache przyjazne wersji jak:

int count = 0; 

for (int i = 0; i < 10; i++) 
{ 
    if (class->flag/*offset:1*/ == true && class->flag2 == true/*offset:2*/) 
      count += class->n; /*offset: 3*/ 

} 

Ta ostatnia pętla potrzebuje tylko do odczytu jednej linii cache za iteracji. Nie może być szybciej.

+1

Spakowanie razem "gorących" członków to mikrooptymalizacja, która po prostu nie jest tego warta. W przypadku współbieżnych struktur danych jest to także pesymizacja (z powodu fałszywego współdzielenia), co jest wprawdzie rzadkim przypadkiem, ale wzmacnia fakt, że nie jest to uniwersalna zasada. –

+0

@ MatthieuM. Masz rację co do fałszywego współdzielenia, dlatego powiedziałem "zasada" (próbowałem sprawić, by brzmiało, że nie jest to prawdziwa zasada, taka jak ta, której używał), ale nie udało mi się. Pracuję w miejscu, w którym opóźnienia są niedozwolone, więc to jest optymalizacja, której używam, nie każdy tego potrzebuje, zgadzam się. Jeśli wiesz, że wiele wątków będzie zapisywać/czytać niektóre współdzielone dane jednocześnie, oczywiście użyjesz innego projektu. – James

Powiązane problemy