-2

Consider the code below (note: after rightful criticism I reworded the question):

#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 has an instance of DataSuper called data. Sub, a subclass of Super, has the same object data, but it is an instance of DataSub, which inherits from DataSuper.

In essence, I would like to access data.a from class Sub. I know I can do it with having data as a pointer and then use dynamic_cast, but not sure if this is good practice.

Is there a way I can avoid it WITHOUT having data as a pointer?

Chicoscience
  • 975
  • 1
  • 8
  • 18
  • 3
    Your use of `static_cast` is misguided. `Super::data` _is not_ a `DataSub` instance. It is a `DataSuper` instance. Telling the compiler to treat it as though it's a `DataSub`, which takes up more space, is asking for trouble. You need to rethink your design. – Nathan Pierson Feb 17 '23 at 22:32
  • 1
    Additionally, the lack of a virtual destructor results in undefined behavior. – Sam Varshavchik Feb 17 '23 at 22:33
  • There's other issues with this code than potential memory leaks. https://godbolt.org/z/cqqv3WK7v – Retired Ninja Feb 17 '23 at 22:33
  • Segmentation fault and memory leaks are orthogonal concepts. You seem to be conflating them. Seg fault comes from accessing a resource you do not have and a memory leak results from not putting away resources that you do have. A seg fault can result in a memory leak if the program crashes before it can put resources away, but you've got bigger problems than the leak if the program is crashing. – user4581301 Feb 17 '23 at 22:36
  • Is there a way to ensure that the object is an instance of DataSuper without static_cast? – Chicoscience Feb 17 '23 at 22:36
  • *Is there a way to ensure that the object is an instance of DataSuper without static_cast?* Yes. And, if this isn't just some obtuse programming sudoku, that's what you should do. (But it will slice.) – Eljay Feb 17 '23 at 22:37
  • Ok, it is not, it is just that I would like to do this without using pointers, and I am completely unaware on how to do it. Having only data in the superclass, but having the constructor by reference, would it work? – Chicoscience Feb 17 '23 at 22:39
  • RIght now, as designed, `Super::data` is _always_ an instance of `DataSuper` and _never_ an instance of `DataSub`, no matter what casts you do. If you want the actual type of `Super::data` to vary, then you need a layer of indirection like using a `std::unique_ptr` or similar. – Nathan Pierson Feb 17 '23 at 22:39
  • The shown code expects objects in C++ to work like they do in Java or C#. C++ is not Java or C#, and objects don't work this way, on a fundamental level. – Sam Varshavchik Feb 17 '23 at 22:39
  • 1
    Please study C++ from [a good book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) instead of guessing how it should work. You will save yourself a lot of misery if you start from a solid foundation. – Yksisarvinen Feb 17 '23 at 22:40
  • I am going then to rephrase this question differently. – Chicoscience Feb 17 '23 at 22:45
  • *I would like to do this without using pointers* It is okay to use pointers as **private member variables**, because the code will strictly control how they are used, so you can *programmatically* ensure they are being used as expected. – Eljay Feb 17 '23 at 22:48
  • No problem as keeping them private - the public example is just a way to make the sample code simpler. – Chicoscience Feb 17 '23 at 22:51
  • 1
    Related: [What is object slicing?](https://stackoverflow.com/questions/274626/) `Sub`'s constructor is *slicing* its `i` parameter when assigning it to the `Super::data` member – Remy Lebeau Feb 17 '23 at 23:55

1 Answers1

2

Super::data isn't a DataSub, but you're treating it like it is one.

Remember, in C++ a variable of object type is the object. It is not a reference or a pointer or anything like that unless you declare it to be. Super::data is a DataSuper and it can never be anything else. Forcibly pointing a DataSub& at it like you have here will not end well.

If you want to share a DataSub between your Super and Sub classes you'll need to use a pointer. For example:

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

Demo


If you want to avoid the extra allocation for data that this requires, you could reverse the ownership direction. That is, have Sub pass a non-owning pointer to a DataSub to Super's constructor and have Sub own the object:

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

Demo

Note that this approach is slightly less safe than the first approach since Sub::data isn't yet initialized when Sub's Super subobject gets initialized. If you try to use the object pointed to by Super::data in Super's constructor you'll quickly wander into the land of undefined behavior. The same goes for Super's destructor. Sub::data gets destroyed before the body of Super::~Super gets executed, so attempting to access the object pointed to by data from Super's destructor body will also result in undefined behavior.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • Thank you so much, this was very helpful. I changed my code to use your first suggestion, unique_ptr and make_unique and it worked like a charm. – Chicoscience Feb 18 '23 at 02:26