Требует ли неполиморфное наследование такой корректировки указателя? Во всех случаях, когда я видел эту настройку указателя, обсуждались примеры использования полиморфного наследования через ключевое слово. virtual
,
Мне не ясно, потребует ли эта неполная корректировка указателя для неполиморфного наследования.
Очень простой пример:
struct Base1 {
void b1() {}
};
struct Base2 {
void b2() {}
};
struct Derived : public Base1, Base2 {
void derived() {}
};
Приведет ли следующий вызов функции к корректировке указателя?
Derived d;
d.b2();
В этом случае такая настройка указателя будет явно излишней, поскольку нет доступа к элементам данных. С другой стороны, если унаследованные функции обращаются к элементам данных, эта корректировка указателя может быть хорошей идеей. С другой стороны, если функции-члены не являются встроенными, кажется, что эта настройка указателя необходима, несмотря ни на что.
Я понимаю, что это деталь реализации, а не часть стандарта C ++, но это вопрос о том, как ведут себя настоящие компиляторы. Я не знаю, имеет ли это место, например, vtables, где все компиляторы придерживаются одной и той же общей стратегии, или я задал очень зависимый от компилятора вопрос. Если это очень зависит от компилятора, то это само по себе будет достаточным ответом или, если вы предпочитаете, вы можете сосредоточиться либо на gcc, либо на clang.
Расположение объектов не указано языком. Из проекта стандарта C ++ N3337:
10 производных классов
5 Порядок, в котором подобъекты базового класса распределяются в наиболее производном объекте (1.8), не указан. [ Замечания: производный класс и его подобъекты базового класса могут быть представлены направленным ациклическим графом (DAG), где стрелка означает «непосредственно производный от». DAG подобъектов часто называют «решеткой подобъектов».
6 Стрелкам не нужно иметь физическое представление в памяти. —Конечная записка]
Подходя к вашему вопросу:
Приведет ли следующий вызов функции к корректировке указателя?
Это зависит от того, как компоновщик создает макет объекта. Это может или не может.
В вашем случае, поскольку в классах нет данных-членов, нет виртуальных функций-членов, и вы используете функцию-член первого базового класса, вы, вероятно, не увидите никаких настроек указателя. Однако, если вы добавите данные-члены и используете функцию-член второго базового класса, вы, скорее всего, увидите изменения указателя.
Вот некоторый пример кода и результат выполнения кода:
#include <iostream>
struct Base1 {
void b1()
{
std::cout << (void*)this << std::endl;
}
int x;
};
struct Base2 {
void b2()
{
std::cout << (void*)this << std::endl;
}
int y;
};
struct Derived : public Base1, public Base2 {
void derived() {}
};
int main()
{
Derived d;
d.b1();
d.b2();
return 0;
}
Выход:
0x28ac28 0x28ac2c
Это не только зависит от компилятора, но и от уровня оптимизации. Как правило, все this
указатели корректируются, только иногда они равны 0, как было бы вашим примером во многих компиляторах (но определенно не во всех — IIRC, MSVC является заметным исключением). Если функция встроена и не имеет доступа this
, тогда настройка может быть полностью оптимизирована.
Используя метод Р. Саху для проверки этого, похоже, что ответ для gcc, clang и icc — да, эта корректировка указателя происходит, если только базовый класс не является первичным базовым классом или пустым базовым классом.
Тестовый код:
#include <iostream>
namespace {
struct Base1
{
void b1()
{
std::cout << "b1() " << (void*)this << std::endl;
}
int x;
};
struct Base2
{
void b2()
{
std::cout << "b2() " << (void*)this << std::endl;
}
int x;
};
struct EmptyBase
{
void eb()
{
std::cout << "eb(): " << (void*)this << std::endl;
}
};
struct Derived : private Base1, Base2, EmptyBase
{
void derived()
{
b1();
b2();
eb();
std::cout << "derived(): " << (void*)this << std::endl;
}
};
}
int main()
{
Derived d;
d.derived();
}
Анонимное пространство имен используется для обеспечения внутренней связи базовых классов. Интеллектуальный компилятор может определить, что единственное использование базовых классов в этом модуле перевода, и эта настройка указателя не требуется. Частное наследство используется для хорошей меры, но я не думаю, что оно имеет реальное значение.
Пример вывода g ++ 4.9.2:
b1() 0x7fff5c5337d0
b2() 0x7fff5c5337d4
eb(): 0x7fff5c5337d0
derived(): 0x7fff5c5337d0
Пример выхода clang 3.5.0
b1() 0x7fff43fc07e0
b2() 0x7fff43fc07e4
eb(): 0x7fff43fc07e0
derived(): 0x7fff43fc07e0
Пример вывода icc 15.0.0.077:
b1() 0x7fff513e76d8
b2() 0x7fff513e76dc
eb(): 0x7fff513e76d8
derived(): 0x7fff513e76d8
Все три компилятора настраивают этот указатель на b2()
, Если в этом простом случае они не исключают эту настройку указателя, то, скорее всего, они никогда не будут пропускать эту настройку указателя. Основной базовый класс и пустые базовые классы являются исключениями.
Насколько я знаю, интеллектуальный компилятор, соответствующий стандартам, может исключить эту настройку указателя для b2()
но это просто оптимизация, которую они не делают.