Есть ли штраф за возвращение членов вложенных объектов?

Рассмотрим следующий код, касающийся вложенного доступа членов:

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

Я могу представить здесь два разных случая:

1. Нет разницы

Насколько я понимаю, не должно быть никакой разницы, поскольку все функции возвращают значение, которое имеет постоянную позицию во время компиляции по отношению к this / в макете памяти класса.

Я ожидаю, что компилятор распознает это.

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

2. Указатель на косвенность

Вполне возможно, что вся «косвенность» осуществляется здесь.
В ind2 например:

получить это -> получить относительную позицию instance_B -> получить относительную позицию m_A_of_B -> вернуть m_A


Вопросы

  1. Это зависит от компилятора, как обрабатывается этот вложенный доступ?
  2. Есть ли разница в этих трех функциях?

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

Извините, если об этом уже спрашивали, если возможно, укажите на соответствующий ответ.

PS: Вам не нужно давать намеки на «преждевременную оптимизацию, являющуюся корнем всего зла» или на профилирование. Я могу составить профиль по этому вопросу, используя компилятор, с которым я разрабатываю, но программа, к которой я стремлюсь, может быть скомпилирована любым соответствующим компилятором. Так что даже если я не смогу определить какие-либо различия, они все равно могут присутствовать.

1

Решение

Стандарт не накладывает никаких ограничений на это. Автор компилятора
с действительно искривленным умом может, например, создать цикл
который ничего не делает в начале каждой функции, с
количество раз в цикле в зависимости от количества
буквы в названии функции. Полностью соответствует, но … я скорее
сомневаюсь, что у него будет много пользователей для его компилятора.

На практике это (едва ли) возможно, что компилятор
отработать адрес каждого подобъекта; например на интеле
что-то вроде:

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 с
константа, например; в этом случае он просто будет использовать
постоянный, и не будет никакого доступа, что когда-либо. По факту,
Вы ошибаетесь, опасаясь такого уровня оптимизации, потому что
компилятор позаботится об этом за вас лучше, чем вы можете.

1

Другие решения

Мое понимание — нет никакой разницы.

Если у вас есть (например) объект D в стеке, то доступ к любому члену или вложенному члену — это просто смещение стека. Если объект D находится в куче, то это смещение указателя, но на самом деле не отличается.

Это связано с тем, что объект D напрямую содержит всех своих членов, каждый из которых содержит своих собственных членов.

1

Затраты отсутствуют, если объект является прямым членом (то есть не указателем или ссылочным элементом), компилятор просто вычисляет соответствующее смещение, независимо от того, есть ли у вас один, два, три или пятьдесят четыре уровня вложенности [предполагается, что вы используете достаточно разумный компилятор — как говорится в комментарии, ничто не останавливает какой-то упрямый компилятор от создания ужасного кода в этом случае — это относится ко многим случаям, когда вы можете с некоторым опытом догадаться, что будет делать компилятор — очень мало вещей, где C ++ стандартные требования, что компилятор не может добавить дополнительный код, который не делает ничего особенно полезного].

Очевидно, что ссылка или член-указатель будут иметь дополнительные затраты на чтение адреса для реального объекта.

1