views:

481

answers:

3

In my code, I have a vector of Student objects.

vector<Student> m_students;

I want to:

  1. Check to see if the vector contains any Student of a certain name.
  2. If no such Student exists, add a new one.
  3. Add data to the Student of that name.

Consider the following code:

// Check to see if the Student already exists.
Student* targetStudent = NULL;
for each (Student student in m_students)
{
 if (student.Name() == strName)
 {
  targetStudent = &student;
  break;
 }
}

// If the Student didn't exist, add it.
if (targetStudent == NULL)
{
 targetStudent = new Student(strName);
 m_students.push_back(*targetStudent);
}

// Add the course info to the Student.
targetStudent->Add(strQuarter, strCourse, strCredits, strGrade);

When I make the call to m_students.push_back(*targetStudent); it seems that the vector "m_students" ends up with a copy of the Student object that "targetStudent" points to at that time.

The subsequent attempt to add to targetStudent does not change the object contained in the vector.

How can I, starting with a pointer to an object, add that object to a vector and then access the object that is in the vector?

+4  A: 

STL containers copy the objects they contain. There is no way to work around this.

You can, however, have a std::vector<std::tr1::shared_ptr<Student> >, which allow you to have a container of smart pointers. For this to work, though, your objects must all be attached to the shared_ptr at the time of construction.

So, something like:

std::vector<std::tr1::shared_ptr<Student> > m_students;

std::tr1::shared_ptr<Student> targetStudent;
for each (std::tr1::shared_ptr<Student> student in m_students)
{
        if (student->Name() == strName)
        {
                targetStudent = student;
                break;
        }
}

// If the Student didn't exist, add it.
if (!targetStudent)
{
        // creates a new Student and attaches it to smart pointer
        targetStudent.reset(new Student(strName));
        m_students.push_back(targetStudent);
}

If you don't have std::tr1::shared_ptr, you can use boost::shared_ptr too; download from Boost.

Chris Jester-Young
I would recommend always using ::std instead of std when referring to the standard namespace. If someone ever makes their own namespace called 'std' (not top-level of course, that wouldn't be legal) then you may end up with ambiguous or surprisingly resolved references to std in some cases.
Omnifarious
+4  A: 
torque
This is a much simpler change than the `shared_ptr` proposal, and better for that reason alone.
MSalters
I reread this answer just now, and agree that I like it too. +1 I also agree with what MSalters said, and even though I wrote the `shared_ptr` answer, I would have no problems if the OP chose this answer or Jerry's answer, both of which are excellent. :-)
Chris Jester-Young
+1 This is actually very similar to what I ended up doing after Chris pointed out that the container will always make a copy.Thanks also for pointing out the memory leak (I am used to garbage collected languages and do not often have to think about the heap.)
Paul Williams
+4  A: 

You've already gotten a reasonably direct answer to your question. Based on what you seem to be trying to accomplish, however, it seems to me that a less direct answer may really be a better one.

At least as I read your description, you have a number of unique students, and a number of courses for each. When a student has completed a course, you want to look for the student. If they're not in the collection, add them. Then add the data for the course they completed.

That being the case, a vector strikes me as a less than ideal solution. You could implement code a couple of different ways, but I'd probably do it like this:

struct course { 
    std::string Quarter_, Course_, Credits_, Grade_;

    using std::string;
    course(string const &q, string const &c, string const &cr, string const &g) 
        : Quarter_(q), Course_(c), Credits_(cr), Grade_(g)
    {}
};

std::map<std::string, std::vector<course> > m_students;

Using this, your entire sequence to look up a student, insert a new student if there isn't one by that name, then adding the course work to the (new or existing) student's record would work out as:

m_students[strName].push_back(course(strQuarter, strCourse, strCredits, strGrade));

Getting back to your original question, the standard containers are intended to work with values. You pass a value to them, and they store a copy of that value. One consequence of that is that anything like push_back(new XXX) is essentially always a mistake (pretty much a guaranteed memory leak). If you have an object, just pass it. If you don't, just create a temporary and pass that. In Java (for one example) seeing new XXX all over the place is routine and nearly unavoidable. While you can write C++ that way as well, it's not something you should expect as a rule.

Jerry Coffin
+1 Yay for looking at the big picture! :-D
Chris Jester-Young
+1 You are right that this is more like what I would want...if my goal was to create a real application. Unfortunately, this was for a school assignment and the professor was very specific about what data structures we should use and there wasn't room for this kind of creativity.
Paul Williams