1

This answer suggests that object slicing in vectors can be overcome with the use of pointers. From my testing, this is true when working with variables shared between the base and derived classes. For example, given these classes:

class Base {
public:
     int x = 1;
};

class Derived : public Base {
public:
    Derived() : Base() { 
        x = 2;
    }
    int y;
};

it can be seen that Derived redefines x to be 2 upon construction. When placed into a vector of the Base class, the x variable behaves as expected.

vector<unique_ptr<Base>> baseVec;               // Vector of pointers to Base objects
unique_ptr<Derived> derivedPtr(new Derived());  // Pointer to Derived object
baseVec.push_back(std::move(derivedPtr));       // Add pointer with std::move()
std::cout << baseVec[0]->x << std::endl;        // Outputs 2

However, attempting to use the y variable belonging only to Derived leads to an error, C2039: 'y' is not a member of 'Base' as seen here:

std::cout << baseVec[0]->y << std::endl;

Is there a way to bypass this issue while preserving member variables unique to derivative classes, and if not is there a superior way to store objects in an ordered container?

Edit

Relevant contents of the previous edit have been moved to my answer below. I do not plan to accept the answer until I can find a way to use smart pointers (or ensure that my raw pointer implementation does not have memory issues).

Nullsrc
  • 33
  • 7
  • What about a [`dynamic_cast`](http://en.cppreference.com/w/cpp/language/dynamic_cast)? – txtechhelp Jul 11 '17 at 04:21
  • The additions made by `Derived` are preserved; `Base` simply doesn't know about them and can't access them. The compiler doesn't know whether an element is going to be a `Base`, a `Derived`, or a `AlsoDerived` that has a `z` instead of a `y`, so the compiler falls back on what it does know: At the very least it's a `Base`. `Dynamic_cast` has been brought up, but you can also use a virtual method to perform the specialized behaviours of the derived classes without having to know exactly what they are. – user4581301 Jul 11 '17 at 04:27
  • Other than that, you have `unique_ptr` in play so there isn't much you can do that's better for safe and effective storage. – user4581301 Jul 11 '17 at 04:30
  • Does `unique_ptr` play nice with `dynamic_cast`? I'm trying to test it out but I'm a little tangled up in the new concept. – Nullsrc Jul 11 '17 at 04:34
  • FYI `x` isn't redefined in `Derived`, it is assigned a different value after initialization – Passer By Jul 11 '17 at 05:24
  • Probably the cleanest use of `dynamic_cast` for this looks something like `std::cout << dynamic_cast(*baseVec[0]).y << std::endl;`. It's a pretty ham-fisted and not very versatile solution. – user4581301 Jul 11 '17 at 05:49
  • @user4581301: dynamic_cast won't work here because you don't have a virtual function and unique pre will call the wrong destructor, because the destructor is not polymorphic. – MikeMB Jul 11 '17 at 06:08

2 Answers2

1

Is there a way to bypass this issue while preserving member variables unique to derivative classes, and if not is there a superior way to store objects in an ordered container?

There are a bunch of techniques with which you can achieve something similar. All of them have already been mentioned in different answers here on SO.
A brief (probably incomplete) resume:

  • Double dispatching. Entirely based on polymorphism, the basic idea is that you pass a visitor class to instances stored in your container and they promote themselves passing the derived type back to the visitor through an overloaded method.

  • Type erasure. You can do that in several manners. As an example, you can store you derived classes as pointers to void or to a base class along with a pointer to a function that receives the erased type. The pointer to function should be the address of a specialization of a function template that is able to static cast back the class to it's original type and perform any task with it.

  • Tagged union. You don't store your derived classes directly. Instead you wrap them in a dedicated struct that carries enough information to cast them back to the derived type through a set of tags (namely an anonymous enum).

  • std::variant. Since C++17 you can use std::variant, that is a kind of type-safe tagged union you can access through a dedicated visitor.

  • AOB (Any Other Business).

I don't think that repeating the examples already available on SO would worth it. You can search them using the keywords above and get what you are looking for.
Each technique has its pros and it cons. Some of them let you extract the original type in the context of the caller, so you can pass the objects to other functions if needed. Some others erase the type completely and provide you with handlers to perform operations on the original type, but you never get it back in the context of the caller. These differences often affect performance (in a way hardly you can observe) because of the feature of the language in use and the actors they pull in.

skypjack
  • 49,335
  • 19
  • 95
  • 187
0

Changing the classes

The class definitions for Base and Derived should be changed to:

class Base {
public:
    int x = 1;

    virtual void f() { }
};

class Derived : public Base {
public:
    Derived() : Base() { x = 2; }
    int y = 55;

    void f() { }
};

The notable addition is the virtual function void f() to allow dynamic_cast to handle casting the pointers.

Using unique_ptr

Unique pointers can be used to store, retrieve, modify and safely delete pointers to Derived objects residing within a vector of pointers to Base objects. The example below uses heap-allocated objects.

/* Insertion */
// Create a vector of unique_ptr to Base and add a unique_ptr to Derived to it
std::vector<std::unique_ptr<Base>> v;
std::unique_ptr<Derived> p1(new Derived());
v.push_back(std::move(p1));

/* Retrieval */
// Release the pointer at base to place it into p2
std::unique_ptr<Derived> p2(dynamic_cast<Derived*>(v[0].release()));

/* Modification */
p2->x = 0xff;    // Modify x (Decimal 255)
p2->y = 0xffff;  // Modify y (Decimal 65535)
int y = p2->y;   // Copy the value of the Derived object's y to an int for use later

// Move the pointer to the modified object back to v
v[0] = std::move(p2); 

/* Output */
std::cout << v[0]->x << std::endl; // Outputs 255
std::cout << y << std::endl;       // OUtputs 65535

/* Delete */
// To safely delete the object, it must be treated as a pointer to a Derived object
// Thus it must be released from the control of v and deleted via p3, which is cast in the same manner as p2 was
std::unique_ptr<Derived> p3(dynamic_cast<Derived*>(v[0].release()));
p3.reset(); // This deletes the object as well, not just the reference to it!

Functions as expected and outputs 255 and 65535.

rand'Chris
  • 788
  • 4
  • 17
Nullsrc
  • 33
  • 7