Рассмотрим следующий код, касающийся вложенного доступа членов:
struct A
{
size_t m_A;
};
struct B
{
A m_A_of_B;
};
class D
{
B instance_B;
A instance_A;
size_t m_D;
public:
size_t direct (void) { return m_D; }
size_t ind1 (void) { return instance_A.m_A; }
size_t ind2 (void) { return instance_B.m_A_of_B.m_A; }
};
Я могу представить здесь два разных случая:
Насколько я понимаю, не должно быть никакой разницы, поскольку все функции возвращают значение, которое имеет постоянную позицию во время компиляции по отношению к this
/ в макете памяти класса.
Я ожидаю, что компилятор распознает это.
Поэтому я предполагаю, что нет никакого наказания за возвращение членов из таких вложенных структур, как я показал выше (или даже глубже).
Вполне возможно, что вся «косвенность» осуществляется здесь.
В ind2 например:
получить это -> получить относительную позицию instance_B -> получить относительную позицию m_A_of_B -> вернуть m_A
Я спрашиваю об этом, поскольку у меня есть только предположение по этому вопросу из того, что я знаю о том, как все работает. Поскольку некоторые из моих предположений в прошлом оказались неверными, я хочу попросить вас об этом.
Извините, если об этом уже спрашивали, если возможно, укажите на соответствующий ответ.
PS: Вам не нужно давать намеки на «преждевременную оптимизацию, являющуюся корнем всего зла» или на профилирование. Я могу составить профиль по этому вопросу, используя компилятор, с которым я разрабатываю, но программа, к которой я стремлюсь, может быть скомпилирована любым соответствующим компилятором. Так что даже если я не смогу определить какие-либо различия, они все равно могут присутствовать.
Стандарт не накладывает никаких ограничений на это. Автор компилятора
с действительно искривленным умом может, например, создать цикл
который ничего не делает в начале каждой функции, с
количество раз в цикле в зависимости от количества
буквы в названии функции. Полностью соответствует, но … я скорее
сомневаюсь, что у него будет много пользователей для его компилятора.
На практике это (едва ли) возможно, что компилятор
отработать адрес каждого подобъекта; например на интеле
что-то вроде:
D::direct:
mov eax, [ecx + offset m_D]
return
D::ind1:
lea ebx, [ecx + offest instance_A]
mov eax, [ebx + offset m_D]
return
D::ind2:
lea ebx, [ecx + offset instance_B]
lea ebx, [ebx + offset m_A_of_B]
mov eax, [ebx + offset m_D]
return
Фактически, все компиляторы, которые я когда-либо видел, работают
полный макет непосредственно содержащихся объектов, и будет
генерировать что-то вроде:
D::direct:
mov eax, [ecx + offset m_D]
return
D::ind1:
mov eax, [ecx + offset instance_A + offset m_D]
return
D::ind2:
mov eax, [ecx + offset instance_A + offset m_A_of_B + offset m_D]
return
(Сложение смещений в квадратных скобках происходит в
ассемблер; выражения соответствуют одной константе
в инструкции в реальном исполняемом файле.)
Итак, отвечая на ваши вопросы: 1 это то, что это полностью
зависит от компилятора, и 2 в том, что на практике
быть абсолютно без разницы.
Наконец, все ваши функции встроены. И они просты
Достаточно того, что каждый компилятор будут встроить их, по крайней мере, с любым
Степень оптимизации активирована. И однажды
Оптимизатор может найти дополнительные оптимизации: он может
обнаружить, что вы инициализировали D :: instance_B :: m_A_of_B :: m_A с
константа, например; в этом случае он просто будет использовать
постоянный, и не будет никакого доступа, что когда-либо. По факту,
Вы ошибаетесь, опасаясь такого уровня оптимизации, потому что
компилятор позаботится об этом за вас лучше, чем вы можете.
Мое понимание — нет никакой разницы.
Если у вас есть (например) объект D в стеке, то доступ к любому члену или вложенному члену — это просто смещение стека. Если объект D находится в куче, то это смещение указателя, но на самом деле не отличается.
Это связано с тем, что объект D напрямую содержит всех своих членов, каждый из которых содержит своих собственных членов.
Затраты отсутствуют, если объект является прямым членом (то есть не указателем или ссылочным элементом), компилятор просто вычисляет соответствующее смещение, независимо от того, есть ли у вас один, два, три или пятьдесят четыре уровня вложенности [предполагается, что вы используете достаточно разумный компилятор — как говорится в комментарии, ничто не останавливает какой-то упрямый компилятор от создания ужасного кода в этом случае — это относится ко многим случаям, когда вы можете с некоторым опытом догадаться, что будет делать компилятор — очень мало вещей, где C ++ стандартные требования, что компилятор не может добавить дополнительный код, который не делает ничего особенно полезного].
Очевидно, что ссылка или член-указатель будут иметь дополнительные затраты на чтение адреса для реального объекта.