2016-03-22 5 views
25

Это продолжение к другому моему вопросу: What is the optimal order of members in a class?Имеет ли общественное и частное влияние на макет памяти объекта?

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

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; 
}; 

Есть ли разница между этим классом и классом, в котором я делаю всех участников общедоступными во время выполнения? Я хочу следовать правилу упорядочения типов от большого к маленькому (если читаемость не серьезно пострадала).

Я предполагаю, что это совсем не влияет на скомпилированную программу, точно так же, как const проверяется только во время компиляции. Это верно?

+0

Избегайте «защищенных», избегайте голых указателей. –

+0

Практически дубликат: http://stackoverflow.com/q/15763091/560648 Пожалуйста, найдите перед тем, как спросить. –

ответ

32

Ответ зависит от языковой версии, поскольку это изменилось с C++ 03 на C++ 11.

В C++ 03, правило было:

Члены внутри блока управления такой же доступ (то есть, от одного из public, protected, private ключевых слов к следующему из этого набора) являются распределяться в порядке декларирования внутри класса, не обязательно смежным.

В C++ 11, правило было изменено следующим образом:

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

Таким образом, в C++ 03, вы можете гарантировать, что это (я использую @ означать смещение члена в классе):

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

В C++ 11 у вас есть еще несколько гарантий:

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

В обеих версиях, компилятор может изменить порядок членов в разных цепях, как он считает нужным, и он может даже чередовать цепи.


Обратите внимание, что есть еще один механизм, который может войти в картину: классы стандартной компоновки.

Класс является стандартным макетом, если у него нет виртуальных машин, если все его нестатические элементы данных имеют одинаковое управление доступом, у него нет базовых классов или нестатических элементов данных типа нестандартного макета или ссылочного типа, и если он имеет не более одного класса с любыми нестатическими членами данных в своей цепочке наследования (т.е.он не может определять свои собственные нестатические элементы данных и наследовать некоторые из базового класса).

Если класс является стандартным макетом, есть дополнительная гарантия того, макет класса).

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

+0

Есть ли у вас данные о том, как компиляторы обычно заказывают участников на практике? –

+0

@JanDvorak Нет, извините. Я не программирую системы, в которых упорядочение элементов данных было бы очень важно, поэтому у меня никогда не было причин искать это. Я бы сказал, что документы платформы ABI и документы компилятора могут быть хорошей отправной точкой для выяснения. – Angew

4

Я бы сказал, что правило заказа не всегда является лучшим. Единственное, что вы делаете, это избегать «заполнения».

Однако, другое «правило», которое следует соблюдать, содержит ваши самые «горячие» элементы сверху, чтобы они могли вписываться в строку кэша, которая обычно составляет 64 байта.

Представьте, что у вас есть цикл, который проверяет флаг вашего класса, а его смещение равно 1, а ваш другой член - на уровне смещения 65, а другой - на расстоянии 200. Вы получите промахи в кеше.

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*/    
} 

Этот цикл будет намного медленнее, чем кэш-дружественной версии, как:

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*/ 

} 

Последний цикл должен только читать одну строку кэша на одну итерацию. Он не может ускориться.

+1

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

+0

@ MatthieuM. Вы правы в ложном обмене, поэтому я сказал «правило» (попытался заставить его звучать, что это не настоящее правило, так же, как тот, который он использовал), но я не думаю. Я работаю в месте, где задержки не допускаются. Это оптимизация, которую я использую, не всем это нужно, я согласен. Если вы знаете, что несколько потоков будут писать/читать некоторые общие данные одновременно, конечно, вы собираетесь использовать другой дизайн. – James

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

  • Нет связанных вопросов^_^