Arrays/vectors without knowing type

  • Thread starter ChrisVer
  • Start date
  • Tags
    Type
In summary, it is not possible to declare an array list or vector in C++ without knowing beforehand the type of objects it may contain. However, 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.
  • #1
ChrisVer
Gold Member
3,378
464
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
  • #2
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;
       };
}
 
  • #3
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.
 
  • #4
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.
 
  • #5
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.
 
  • #7
@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.
 
  • #8
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.
 
  • #9
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.
 

1. What is an array/vector without knowing its type?

An array/vector without knowing its type refers to a data structure that can hold a collection of elements, but the specific type of data for each element is unknown. This means that the elements can be of any type, such as integers, strings, or even other arrays/vectors.

2. How is an array/vector without knowing its type different from a regular array/vector?

The main difference is that a regular array/vector requires all of its elements to be of the same type, while an array/vector without knowing its type allows for different types of elements. This makes it more flexible but also more difficult to work with since the type of each element must be determined before it can be used.

3. Can an array/vector without knowing its type still perform common operations like adding or removing elements?

Yes, it can still perform common operations like adding or removing elements. However, since the type of each element is unknown, these operations may require additional checks or conversions to ensure the correct type is being used.

4. What are some use cases for an array/vector without knowing its type?

An array/vector without knowing its type can be useful in situations where the specific type of data is not important, such as when working with large datasets or when creating generic data structures. It can also be helpful for handling user input or data from external sources where the data type may vary.

5. What are some challenges of working with an array/vector without knowing its type?

One of the main challenges is the need to constantly check and convert the type of each element, which can be time-consuming and error-prone. It can also be difficult to ensure consistency and accuracy in the data when working with different types of elements. Additionally, debugging and troubleshooting can be more complicated since the type of each element may not be immediately obvious.

Similar threads

  • Programming and Computer Science
Replies
8
Views
1K
  • Programming and Computer Science
Replies
15
Views
2K
  • Programming and Computer Science
Replies
22
Views
2K
  • Programming and Computer Science
Replies
23
Views
1K
  • Programming and Computer Science
Replies
2
Views
686
  • Programming and Computer Science
2
Replies
36
Views
3K
  • Programming and Computer Science
2
Replies
36
Views
2K
  • Programming and Computer Science
3
Replies
89
Views
4K
  • Programming and Computer Science
Replies
5
Views
2K
  • Programming and Computer Science
Replies
25
Views
2K
Back
Top