Рассмотрим код ниже (примечание: после справедливой критики я переформулировал вопрос):
#include <vector>
using std::vector;
class DataSuper {
public:
DataSuper() {}
};
class DataSub : public DataSuper {
public:
int a;
DataSub() {}
};
class Super {
public:
DataSuper data;
Super() {}
};
class Sub : public Super {
public:
Sub(DataSub i) {
data = i;
}
void test() {
// I would like to print the value of data.a
}
};
int main(int argc, char *argv[]) {
DataSub dataSub;
Super* s = new Sub(dataSub);
s->test();
delete(s);
return 0;
}
Super имеет экземпляр DataSuper с именем data. Sub, подкласс Super, имеет тот же объект data, но является экземпляром DataSub, который наследуется от DataSuper.
По сути, я хотел бы получить доступ к data.a из класса Sub. Я знаю, что могу сделать это с помощью data в качестве указателя, а затем использовать dynamic_cast, но не уверен, что это хорошая практика.
Есть ли способ избежать этого БЕЗ использования data в качестве указателя?
Кроме того, отсутствие виртуального деструктора приводит к неопределенному поведению.
В этом коде есть и другие проблемы, кроме потенциальных утечек памяти. godbolt.org/z/cqqv3WK7v
Ошибка сегментации и утечка памяти — ортогональные понятия. Кажется, вы их путаете. Ошибка Seg возникает из-за доступа к ресурсу, которого у вас нет, а утечка памяти возникает из-за того, что вы не убираете ресурсы, которые у вас есть. Сбой seg может привести к утечке памяти, если программа выйдет из строя до того, как она сможет выделить ресурсы, но у вас есть более серьезные проблемы, чем утечка, если программа дает сбой.
Есть ли способ убедиться, что объект является экземпляром DataSuper без static_cast?
Есть ли способ убедиться, что объект является экземпляром DataSuper без static_cast? Да. И, если это не просто какое-то тупое программирование судоку, это то, что вы должны сделать. (Но он будет резаться.)
Хорошо, это не так, просто я хотел бы сделать это без использования указателей, и я совершенно не знаю, как это сделать. Будет ли это работать, имея только данные в суперклассе, но имея конструктор по ссылке?
Прямо сейчас, как и было задумано, Super::data всегда является экземпляром DataSuper и никогда экземпляром DataSub, независимо от того, какие приведения вы делаете. Если вы хотите, чтобы фактический тип Super::data менялся, вам нужен уровень косвенности, например, использование std::unique_ptr<DataSuper> или подобного.
Показанный код предполагает, что объекты в C++ будут работать так же, как в Java или C#. C++ — это не Java или C#, и объекты не работают таким образом на фундаментальном уровне.
Пожалуйста, изучите C++ по хорошей книге вместо того, чтобы гадать, как это должно работать. Вы избавите себя от многих страданий, если начнете с прочного фундамента.
Тогда я собираюсь перефразировать этот вопрос по-другому.
Я хотел бы сделать это без использования указателей. Можно использовать указатели как закрытые переменные-члены, потому что код будет строго контролировать их использование, поэтому вы можете программно гарантировать, что они используются должным образом.
Нет проблем с сохранением их конфиденциальности — общедоступный пример — это просто способ упростить пример кода.
Связанный: Что такое нарезка объектов? Конструктор Sub нарезает свой i параметр при назначении его члену Super::data
Super::data не DataSub, но вы относитесь к нему как к Super::data.
Если вы хотите разделить Super между вашими классами Sub и data, вам нужно будет использовать указатель. Например:
class Super
{
public:
std::unique_ptr<DataSuper> data;
Super(std::unique_ptr<DataSuper> data) : data{std::move(data)} {}
};
class Sub : public Super
{
public:
using Super::Super;
private:
// Use this if you need to treat data as a DataSub
DataSub& dataSub()
{
return static_cast<DataSub&>(*data);
}
};
int main()
{
std::unique_ptr<Super> s = std::make_unique<Sub>(std::make_unique<DataSub>());
}
Демо
Если вы хотите избежать дополнительного выделения для Sub, которое для этого требуется, вы можете изменить направление владения. То есть, пусть DataSub передает указатель, не владеющий Super, на конструктор Sub, и Sub::data владеет объектом:
class Super
{
public:
Super(DataSuper* data) : data{data} {}
private:
DataSuper* data;
};
class Sub : public Super
{
public:
Sub() : Super{&data} {}
private:
DataSub data;
};
int main()
{
std::unique_ptr<Super> s = std::make_unique<Sub>();
}
Демо
Обратите внимание, что этот подход немного менее безопасен, чем первый подход, поскольку Sub еще не инициализирован, когда инициализируется подобъект Super Super::data. Если вы попытаетесь использовать объект, на который указывает Super, в конструкторе Super, вы быстро попадете в страну неопределенного поведения. То же самое касается деструктора Sub::data. Super::~Super уничтожается до того, как тело data будет выполнено, поэтому попытка доступа к объекту, на который указывает Super, из тела деструктора Super::data также приведет к неопределенному поведению.
Большое спасибо, это было очень полезно. Я изменил свой код, чтобы использовать ваше первое предложение, unique_ptr и make_unique, и это сработало как шарм.
Ваше использование static_cast ошибочно. Super::data не является экземпляром DataSub. Это экземпляр DataSuper. Сказать компилятору обработать его так, как будто это DataSub, который занимает больше места, значит напрашиваться на неприятности. Вам нужно переосмыслить свой дизайн.