Можем static_cast
от Base*
до Derived*
, но в моем случае не видно связи, когда должен произойти кастинг. Есть ли какой-нибудь трюк (кроме reinterpret_cast
, конечно), чтобы это заработало?
struct Derived;
struct Base
{
Base()
{
//Is there a way to make it work?
//Derived* d = static_cast<Derived*>(this);
}
};
struct Derived: public Base
{
};
int main()
{
Base b{};
// Here it works as the compiler is aware of the relationship
Derived* d = static_cast<Derived*>(&b);
}
Вы можете попробовать здесь.
Обновлять: Спасибо за ваши комментарии. Конечно, я знаю, что мой пример неработоспособен, я просто хотел продемонстрировать проблему. Реальная мировая проблема гораздо сложнее.
У меня есть непрозрачный указатель, который указывает либо на Base
, либо на Derived
(переключатель времени компиляции). И мне нужен этот непрозрачный указатель в конструкторе класса Base
. Когда непрозрачный указатель равен Derived*
, экземпляр Base
не может быть создан только через Derived
, поэтому он на 100% безопасен. В другом случае Base
== Derived
, чтобы приведения не произошло.
Поскольку это непрозрачный указатель, а мы находимся в Base, у нас есть только предварительное объявление Derived. Нравиться:
#ifdef PLATFORM_A
typedef struct Derived UsedType;
#else
typedef struct Base UsedType;
#endif
Поэтому мне понадобится UsedType*
в конструкторе Base, виртуальный это либо Base*
, либо Derived*
. К сожалению, если мы не на PLATFORM_A, тогда Derived вообще не определен, поэтому, когда код достигает конструктора Base, компилятор не может знать о его связи с Derived
(поскольку Derived
может вообще не существовать, и в этом случае приведение будет от Base*
до Base*
).
Обновление 2: Реальный код
Обновление 3: я еще раз обновил код. Теперь есть член, который хранит непрозрачный указатель this. Я не использую его в конструкторе, использую только снаружи. Его необходимо инициализировать в конструкторе (поскольку член является константным).
#include <iostream>
#define PLATFORM_A
#ifdef PLATFORM_A
typedef struct Derived UsedType;
#else
typedef struct Base UsedType;
#endif
struct User
{
UsedType* const platform;
void print() const;
};
struct Base
{
const char* const name{"Base"};
User const iNeedThis;
#ifdef PLATFORM_A
protected:
#endif
Base()
: iNeedThis{(UsedType*)this}
//: iNeedThis{static_cast<UsedType*>(this)}
{
}
};
#ifdef PLATFORM_A
struct Derived: public Base
{
const char* const name{"Derived"};
Derived()
: Base()
{
}
};
#endif
void User::print() const
{
std::cout << platform->name << std::endl;
}
int main()
{
UsedType myObj{};
myObj.iNeedThis.print();
}
Есть ли у вас реальный пример использования, когда вам понадобится доступ к конструктору Derived*
Within Base
? Или это всего лишь мысленный эксперимент?
Невозможно использовать *this
в качестве точки Derived
. потому что объект, находящийся в this
, не является Derived
, поэтому любой доступ к нему как Derived
будет неопределенным поведением, и никакое приведение не сможет это изменить, static_cast
, reinterpret_cast
или unobtainable_cast
.
Я не знаю, каков более широкий контекст. Однако это чем-то напоминает мне любопытно повторяющийся шаблон шаблона Возможно, вам стоит на это обратить внимание. В противном случае мне кажется, что это проблема дизайна на более высоком уровне.
@AviBerger да, я думал о CRTP, но не могу переместить конструктор Base в заголовок :( Да, это своего рода проблема дизайна, но преобразование Base to Derived может ее решить. Но, похоже, мне нужно подумать о План B :)
Принудительно отложить оценку с помощью шаблона конструктора Base. template<typename T = Derived> Base() { static_cast<T*>(this); }
@Broothy Как UsedType
связан с конструктором Base
? Пожалуйста, отредактируйте свой вопрос, чтобы предоставить лучший пример РЕАЛЬНОЙ ПРОБЛЕМЫ, с которой вы столкнулись.
@RemyLebeau конечно. пожалуйста, найдите реальный код во втором обновлении.
@Broothy, как говорилось ранее, когда PLATFORM_A
определен и, следовательно, UsedType
является Derived
, тогда UsedType* iNeedThis = static_cast<UsedType*>(this);
вызовет неопределенное поведение, если вы попытаетесь разыменовать iNeedThis
. До сих пор не ясно, ЗАЧЕМ вам вообще нужен этот приведённый указатель. Что именно конструктор Base
собирается делать с этим указателем? Я начинаю задаваться вопросом, действительно ли это Проблема XY.
@RemyLebeau В этот момент конструктор передает iNeedThis участнику. Он обрабатывается как непрозрачный указатель, поэтому в конструкторе он не разыменовывается. Я мог бы установить его вне конструктора, но это было бы подвержено ошибкам. Я думаю, что пример кода завершен, но вполне возможно, что его невозможно решить таким образом.
@Broothy Даже если вы просто сохраните указатель, я думаю, что он все равно будет UB внутри конструктора Base
, поскольку еще нет объекта Derived
, на который this
мог бы указать. Почему нельзя просто поставить this
позже? Почему его нужно приводить внутри конструктора? Это НЕ минимальный воспроизводимый пример (где находится член? Как он используется? и т. д.)
Есть ли какая-то конкретная причина, по которой вы не делаете того, что вам нужно, полиморфно, используя что-то виртуальное?
@RemyLebeau Я добавил участника по вашему желанию. Мне нужно, чтобы он был инициализирован в конструкторе, поскольку хранящий его член является константным. В конструкторе он обрабатывается как непрозрачный указатель, на этом этапе мы все равно мало что знаем о типе, просто typedef struct Derived UsedType;
если PLATFORM_A
определен.
Чтобы ответить на ваш вопрос: вы не можете привести к неполному типу, поэтому вам нужно будет переместить реализацию Base()
после объявления Derived
, например:
struct Derived;
struct Base
{
Base();
};
struct Derived: public Base
{
};
Base::Base()
{
// see note below!!!
Derived* d = static_cast<Derived*>(this);
}
int main()
{
Base b{};
// see note below!!!
Derived* d = static_cast<Derived*>(&b);
}
При этом то, что вы делаете, рискованно, потому что this
внутри конструктора Base
не указывает на объект Derived
, и b
внутри main()
также не является объектом Derived
. Если вы попытаетесь разыменовать приведенный указатель в любой ситуации, вы вызовете неопределенное поведение.
Невозможно сделать приведение внутри конструктора Base
законным, поскольку ни один объект Derived
просто не будет существовать, пока создается класс Base
.
Но в main()
вы можете изменить b
на указатель на объект Derived
, тогда вы можете безопасно привести этот указатель, например:
int main()
{
Derived obj{};
Base* b = &obj;
// this is perfectly OK!
Derived* d = static_cast<Derived*>(b);
}
Привет, спасибо за ваше предложение. К сожалению, я не могу переместить конструктор Base в точку, где виден Derived, смотрите обновление в описании. Было бы возможно (теоретически) иметь шаблонный конструктор, который получал бы «настоящий» указатель Derived от конструктора Derived, когда он вызывает Base, но, к сожалению, я не могу переместить конструктор в заголовок, поэтому его нельзя использовать в качестве шаблона.
Спасибо за ваше предложение, но, к сожалению, в моем конкретном случае это невозможно. Другой ответ также решает мой особый случай.
Я имею в виду, что сделать буквально то, что вы просили, тривиально, даже не перемещая конструктор:
struct Base
{
UsedType* self();
#ifdef PLATFORM_A
protected:
#endif
Base()
{
UsedType* iNeedThis = self();
}
};
Позже в base.cpp или где угодно:
#ifndef PLATFORM_A
UsedType* Base::self() { return this; }
#endif
и даже позже, в файле Derived.cpp
#ifdef PLATFORM_A
UsedType* Base::self()
{
return static_cast<Derived*>(this);
}
#endif
Но это все еще кажется плохим дизайном.
Ааа здорово, я думал о таком трюке, спасибо!
Это плохой дизайн, но попытка добавить поддержку второй, совершенно другой платформы к базе кода, которая не была спроектирована как мультиплатформенная, без полной переписывания требует таких плохих дизайнерских решений...
Примечание: вряд ли когда-либо понадобится приводить объект к производному классу. Как вы думаете, почему вам это нужно?