Arrays/vectors without knowing type

  • Thread starter Thread starter ChrisVer
  • Start date Start date
  • Tags Tags
    Type
AI Thread Summary
In C++, it's not possible to declare a vector without knowing the type of objects it will contain, but polymorphism allows for a vector of a base class (like Fruits) to hold derived class objects (such as Apple and Orange). A vector of pointers to the base class is recommended to avoid issues with object slicing and memory management. Using smart pointers like std::unique_ptr is advised to manage memory safely and prevent leaks. Alternatives like std::variant or boost::variant can also be explored for heterogeneous containers. Proper implementation ensures that the vector can handle different derived types while maintaining type safety and memory integrity.
ChrisVer
Science Advisor
Messages
3,372
Reaction score
465
Hi,

Is it possible to declare an array list or vector in C++ without knowing beforehand the type of objects it may contain? (in fact it may contain any kind of object)
For example if I have the following classes (maybe there are some mistakes overall, but my main concern is how to deal with the vector TYPE which should either be an Orange or an Apple):
Code:
class Fruits{
   private:
      int m_Nfruit=0;
      //HERE NEED HELP
      vector<TYPE??> fruitContainer;
   public:
      Fruits() { m_Nfruits += 1; };
      virtual ~Fruits() { };
      void Print() const {
            for( std::vector<TYPE>::iterator it = fruitContainer.begin() ; it!=fruitContainer.end() ; it++){
                 it->Print() // should print either Apple or Orange
            }
      };
};

class Orange : public Fruits{
    private:
       int m_Norange = 0;
    public:
       Orange(){
             Fruit::Fruits()
             m_Norange +=1 ; 
             fruitContainer.push_back(*this)
             };
       virtual ~Orange(){};
       void Print() const {
           std::cout << "Orange" << std::endl;
       };
}

class Apple : public Fruits{
    private:
       int m_Napple = 0;
    public:
       Apple(){
             Fruit::Fruits()
             m_Napple +=1 ; 
             fruitContainer.push_back(*this);
             };
       virtual ~Apple(){};
       void Print() const {
           std::cout << "Apple" << std::endl;
       };
}
 
Technology news on Phys.org
ChrisVer said:
Hi,

Is it possible to declare an array list or vector in C++ without knowing beforehand the type of objects it may contain?
No, but with polymorphism, you can create a vector of some base class (such as Fruits in your example), and have it contain objects of various derived classes.
What you have below looks like a step in the right direction.
ChrisVer said:
(in fact it may contain any kind of object)
For example if I have the following classes (maybe there are some mistakes overall, but my main concern is how to deal with the vector TYPE which should either be an Orange or an Apple):
Code:
class Fruits{
   private:
      int m_Nfruit=0;
      //HERE NEED HELP
      vector<TYPE??> fruitContainer;
   public:
      Fruits() { m_Nfruits += 1; };
      virtual ~Fruits() { };
      void Print() const {
            for( std::vector<TYPE>::iterator it = fruitContainer.begin() ; it!=fruitContainer.end() ; it++){
                 it->Print() // should print either Apple or Orange
            }
      };
};

class Orange : public Fruits{
    private:
       int m_Norange = 0;
    public:
       Orange(){
             Fruit::Fruits()
             m_Norange +=1 ;
             fruitContainer.push_back(*this)
             };
       virtual ~Orange(){};
       void Print() const {
           std::cout << "Orange" << std::endl;
       };
}

class Apple : public Fruits{
    private:
       int m_Napple = 0;
    public:
       Apple(){
             Fruit::Fruits()
             m_Napple +=1 ;
             fruitContainer.push_back(*this);
             };
       virtual ~Apple(){};
       void Print() const {
           std::cout << "Apple" << std::endl;
       };
}
 
The problem with the vector is that it should know what it contains though... like int, float, double etc , and in this case oranges or apples...
I thought about moving the container as a member of the derived classes (haven't tried that out yet). So have vector<Orange> or vector<Apple>... and using the same name convention it could be used in the Print function of the Base class.
 
ChrisVer said:
The problem with the vector is that it should know what it contains though... like int, float, double etc , and in this case oranges or apples...
With polymorphism, if you declare the vector in the base class (Fruits), all the vector "knows" is that it will contain objects of the derived classes -- Apple or Orange objects in your example code. The vector won't "care" about the types of the objects it contains, but your code can determine which type of fruit a particular object is by calling the object's Print() method. In the base class, you could make the Print() method virtual, and make sure that any derived class provides an implementation of this method.
ChrisVer said:
I thought about moving the container as a member of the derived classes (haven't tried that out yet). So have vector<Orange> or vector<Apple>... and using the same name convention it could be used in the Print function of the Base class.
This is a different kind of solution, using a templatized vector. The Standard Template Library (STL) vector type you are using is already defined in terms of templates (as in vector<int>). You can extend this idea to create a vector whose base type is Apple or Orange, whatever, but the difference is that each vector will contain only one object type. For more info, see https://msdn.microsoft.com/en-us/library/y097fkab.aspx.
 
The answer to the question you asked is that it is possible to do this with an array of void pointers. However, as pointed out, this is almost certainly not what you want.
 
@Mark44 You should absolutely never use polymorphism in a vector or any other template class.

Here is why:
Code:
struct A {
     int m_a;
     int m_b;
     int m_c;
};

struct B : public A {
     int m_d;
};

std::vector<A> items;
The problem should be obvious. When you push something onto the vector, it'll create a new instance of that object in the vector's internal array. You told the compiler that the vector contains objects of type A, so it will allocate enough space to hold an A. In the example above, A is 3 byte, and B is 4. Your compiler may let you do it, (GCC does) but the results will be undefined so never ever do it, it will work only in the case where the two classes are the exact same size. The vtable may also get screwed up as it will create the new internal copy with A's constructor.If you use polymorphism you have to use a vector of pointers.
Code:
std::vector<A *> items;

Now you can put whatever you want in it because the vector only allocates the space for a new pointer, not a new object. Of course, then you have to be sure to clean up all of the memory yourself:
Code:
std::vector<A *>::iterator i = items.begin();
std::vector<A *>::iterator e = items.end();
for(; i != e; ++i){
     delete (*i);
}

If you want a truly type agnostic vector ala PHP, you'll have to wrap your item in a universal wrapper.
 
newjerseyrunner said:
You should absolutely never use polymorphism in a vector.
or any other template class.

Here is why:
Code:
struct A {
     int m_a;
     int m_b;
     int m_c;
};

struct B : public A {
     int m_d;
};

std::vector<A> items;
The problem should be obvious. When you push something onto the vector, it'll create a new instance of that object in the vector's internal array. You told the compiler that the vector contains objects of type A, so it will allocate enough space to hold an A. In the example above, A is 3 byte, and B is 4. Your compiler may let you do it, (GCC does) but the results will be undefined so never ever do it, it will work only in the case where the two classes are the exact same size. The vtable may also get screwed up as it will create the new internal copy with A's constructor.
I don't see the problem, at least based on your example. The items variable, of type vector<A>, is intended to hold instances of the A object. I wouldn't expect someone to push a B object onto this vector. In any case, your example is more about templates than it is about polymorphism.

In the OP's example, with a Fruits base class, with Apple and Orange classes derived from Fruit. all of the class instances take up the same amount of space in memory: 4 bytes for the private member. If he were to create a vector of Fruits objects, the vector could contain Orange or Apple instances, I believe. I'm using VS, which stores only the member data in a struct or class instance, so at least in this implementation, instances of these three types take up the same amount of memory. The Borland compiler I had years ago also stored the vtable, with pointers to the member functions, but the VS C++ compiler doesn't seem to do this.

BTW, A contains 3 ints, at 4 bytes each, for a total of 12 bytes, and B contains 4 ints, for a total of 16 bytes.
 
Yes, but you are forgetting about the vtable.

In the case of Fruit, if you give an object of type Apple to it. It will call the copy constructor for Fruit. So anything that's not initialized by the Fruit(const Fruit &) function will end up being junk. That's not just missing variables but also vtable references. If you call Print() from any object in that vector, it will call Fruit::Print.
 
  • #10
newjerseyrunner said:
In the case of Fruit, if you give an object of type Apple to it. It will call the copy constructor for Fruit. So anything that's not initialized by the Fruit(const Fruit &) function will end up being junk. That's not just missing variables but also vtable references. If you call Print() from any object in that vector, it will call Fruit::Print.
I suggested earlier that the Print() method on the Fruit class should be virtual, with derived classes implementing this method. Since a Fruit object is not useful, it should probably be defined as an abstract class.

Anyway, I haven't given any real thought to this stuff for about 20 years, but I seem to remember writing some code with an array of some base type into which you could insert objects of the derived types. I'll have to do some more research.
 
Last edited:
  • #11
So if I understand well, if you tell it to expect addresses (pointers) than objects, you can do the job and add elements of the derived classes?
i.e. (not in the class yet but in the code):
Code:
vector<*Fruit> v;
Orange * orange = new Orange()
Apple* apple = new Apple()
v.push_back(orange);
v.push_back(apple);
for( auto it = v.begin(); it != v.end() ; it++){
   //it is a pointer to the pointer of apple or orange, so have to dereference to get the pointer to the actual object
   (*it)->Print(); //prints implementation in Apple/Orange class
}
 
  • #12
ChrisVer said:
So if I understand well, if you tell it to expect addresses (pointers) than objects, you can do the job and add elements of the derived classes?
i.e. (not in the class yet but in the code):
Code:
vector<*Fruit> v;
Orange * orange = new Orange()
Apple* apple = new Apple()
v.push_back(orange);
v.push_back(apple);
for( auto it = v.begin(); it != v.end() ; it++){
   //it is a pointer to the pointer of apple or orange, so have to dereference to get the pointer to the actual object
   (*it)->Print(); //prints implementation in Apple/Orange class
}
Yes, that is correct. Just remember that when you create something with new, you have to explicitly call delete when you are done with it.
 
  • #13
ChrisVer said:
So if I understand well, if you tell it to expect addresses (pointers) than objects, you can do the job and add elements of the derived classes?
i.e. (not in the class yet but in the code):
Code:
vector<*Fruit> v;
Orange * orange = new Orange()
Apple* apple = new Apple()
v.push_back(orange);
v.push_back(apple);
for( auto it = v.begin(); it != v.end() ; it++){
   //it is a pointer to the pointer of apple or orange, so have to dereference to get the pointer to the actual object
   (*it)->Print(); //prints implementation in Apple/Orange class
}

Do not store raw owning pointers in a vector. This obsolete, dangerous code with new and delete will bite you. As other answers have said, when the container goes out of scope, the pointers will be deleted but not the objects they point to. What other answers have not said is that naive manual cleanup of the vector is insufficient because that code will not be executed if an exception occurs. The proper solution is to use a vector of std::unique_ptr.

As for your question, consider using the recently added std::variant or the well-established boost::variant. This topic is anything but new and several solutions exist. If you want to research it further, the search term you are looking for is heterogeneous container.
 
Back
Top