Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ How to properly copy container(vector) of pointers?

A few times I've stumbled across the scenario where I have a container of pointers that needs to be copied.

Let's say we have the following class hierarchy:

  • Student (base class)

    • Freshman (subclass)
    • Sophmore (subclass)
    • Junior (subclass)
    • Senior (subclass)
  • StudentService

The StudentService class has a std::vector<Student*> students field and the following constructor:

StudentService::StudentService(std::vector<Student*> students) {
   // code
}

It won't be correct to just use the std::vector::operator= operator and write this->students = students, because that will only copy the pointer addresses and so if someone from the outside deletes the objects pointed to by those pointers, then the StudentService class would suffer as a result.

The solution is to loop through each pointer in the students parameter and create a new dynamic object, something like this:

for(int i = 0; i < students.size(); i++) {
   this->students.at(i) = new Student(*students.at(i));
}

But even that is not proper due to the fact that it will create ONLY Student objects. And we know that a Student can be a Freshman, Sophmore, Junior or Senior. So here is my question: what's the best solution to this problem?

I guess one way would be to place a private enum field inside each Student class and have 4 if-else statements checking what type of Student it is and then creating a new dynamic object based on that like so:

 for(int i = 0; i < students.size(); i++) {
   if(students.at(i).getType() == FRESHMAN) {
      this->students.at(i) = new Freshman(*students.at(i));
   } else if(students.at(i).getType() == SOPHMORE) {
      this->students.at(i) = new Sophmore(*students.at(i));
   } else if {
   // and so on...
   }
}

But this still seems quite cumbersome, so what would you suggest?

like image 888
Mr. Nicky Avatar asked Oct 10 '17 15:10

Mr. Nicky


People also ask

How do you store vectors of pointers?

You can store pointers in a vector just like you would anything else. Declare a vector of pointers like this: vector<MyClass*> vec; The important thing to remember is that a vector stores values without regard for what those values represent.

What is the main issue with storing objects in the vector class as opposed to pointers?

Having vector of objects is much slower than a vector of pointers. The results are because algorithms such as sorting need to move elements inside the container.

Can we pass pointer to copy constructor?

Passing by references ensures an actual object is passed to the copy constructor, whilst a pointer can have NULL value, and make the constructor fail.


2 Answers

You're looking for the Clone pattern. Add a clone() virtual function to Student, which is overridden in each descendant and creates the appropriate copy. Then write deep copy of containers as you've correctly specified.

Edit: my work assumption is that your Freshman, etc. classes are descending from Student. If not, use a variant<> and apply a copy visitor.

like image 81
lorro Avatar answered Oct 12 '22 11:10

lorro


Resolving ownership issues

If you deem to shared the Student-s between your modules, then you are facing an ownership issue, and I would recommend using vector of std::shared_ptr<Student>-s to solve it.

If you have a std::vector<std::shared_ptr<Student>>, you can pass it to anyone you want. The receiver can copy the vector using an assignment operator and later on any objects he adds/removes won't affect the original container, just like you seem to desire.

Resolving cloning issues

If you are interested in each module having its own copy of a Student-s vector, you are facing a cloning issue.

You could resolve it by adding the following method to your classes:

class Student {
    [..]
    virtual Student * clone() const = 0; // Assuming Student is abstract, otherwise implement as well
};

class Freshman : public Student {
    [..]
    virtual Freshman * clone() const { return new Freshman(*this); }
};

// Same for other derived classes...

And then using std::transform to copy the vector:

// students is the original std::vector<Student *>
std::vector<Student *> copy(students.size());
std::transform(students.begin(), students.end(), copy.begin(), [](Student * s) -> Student * { return s->clone(); });

BTW, it's Sophomore, not Sophmore...

like image 23
Daniel Trugman Avatar answered Oct 12 '22 11:10

Daniel Trugman