Интересно, гарантировало ли это стандартом С++ однократное наследование, чтобы объект "расти" вверх, которому даны class Base
и class Derived: public Base
, а указатель Derived* ptr
, результат dynamic_cast<Base*>(ptr)
будет всегда будет численно меньше или равно ptr
.
Является указателем на базу всегда <= указатель на производный класс?
Ответ 1
Нет. Макет памяти - это детализация реализации.
При этом мне не известно о какой-либо реализации, которая на самом деле этого не делает, если нет виртуальных функций. Если вы вводите виртуальную функцию в Derived
(но ее не было в Base
), указатель виртуальной таблицы мог (в зависимости от реализации) располагаться перед полями Base
(делая Base*
больше, чем Derived*
).
Разъяснение:
Пример выше - это Visual С++. Вы можете проверить это, используя следующий код:
class Base {
int X;
};
class Derived : public Base {
virtual void f() {
}
int Y;
};
int main() {
Derived d;
Derived* d_ptr = &d;
Base* b_ptr = dynamic_cast<Base*>(d_ptr); // static_cast would be enough BTW.
bool base_smaller_or_equal = (ptrdiff_t)b_ptr <= (ptrdiff_t)d_ptr;
return 0;
}
В Visual С++ false
будет false
. Судя по комментарию @enobayram, он должен быть true
под GCC.
В любом случае это деталь реализации, и на нее нельзя полагаться.
Ответ 2
Нет.
Однако пока не нужно паниковать. Этот вопрос, хотя и не полностью рассмотренный в стандарте С++, отвечает за документ ABI, за которым последует компилятор. Многие компиляторы, такие как gcc, Clang или icc (но не VС++), следуют Itanium ABI.
В Itanium ABI, если у вас есть одно не виртуальное наследование, а базовый класс имеет виртуальный метод, то Derived и Base всегда будут иметь одинаковый адрес.
Это, как говорится, это реализация, о которой вам действительно не нужно беспокоиться. Вы можете отлично писать С++ стандартно совместимый код и по-прежнему управлять своими вариантами использования. Дело в том, что С++ позволяет использовать любой указатель на void*
и char*
(последний как конкретное исключение для сглаживания) и обратно. Единственное, о чем вам нужно беспокоиться, это то, что когда вы делаете a Base*
до void*
, вам нужно вернуть его обратно в Base*
, а не в Derived*
. То есть тип, который вы вставляете, и тип, который вы возвращаете, должны совпадать.
Намного сложнее, однако, знать (точно) размер объекта. Это требует применения sizeof
к текущему динамическому типу объекта, и нет возможности получить его virtually
.
Мой совет будет состоять в том, чтобы на самом деле измерить ваш базовый класс:
class Base {
public:
char const* address() const { return (char const*)dynamic_cast<void const*>(this); }
size_t offset() const { return this->address() - (char const*)this; }
virtual size_t size() const { return sizeof(Base); } // to be overriden
virtual ~Base() {}
};
Это помогает получить всю необходимую информацию (см. демонстрацию). Обратите внимание, что с Itanium ABI offset()
всегда будет возвращаться 0
, но по крайней мере вы здесь не предполагаете.