简体   繁体   中英

C++ STL Containers and references

I have a misunderstanding of how does work references, STL containers and Objects.

I previously look for using references in an STL container, but it seems that STL containers are "objects that store objects". But if I want to store objects and modifying them in the container, how can i do it ?

I pasted a small snipet of code illustrating my issue.

class MyObject {
    public : 
        int value;

    MyObject(const MyObject& right) : value(right.value) {}
    MyObject(int _value) : value(_value) {}

    bool operator< (const MyObject& right) const {
        return value < right.value;
    }

    void display() const {
        cout << "(" << value << ")  ";
    }
};

And in the main section

cout << "Creating ... " << endl;
set<MyObject> oset;

for (int i = 0 ; i < 10 ; i++) {
     MyObject o(rand() % 1000);
     oset.insert(o);
}
cout << endl;

cout << "Display before ... " << endl;
for (MyObject o : oset)  o.display();
cout << endl;
cout << "Incrementing ... " << endl;
for (MyObject o : oset)  o.value += 1000;
cout << "Display after ... " << endl;
for (MyObject o : oset) o.display();
cout << endl;

Since the container does not use references, the incrementation is applied on copies and not the objects.

I tried to use '&' insided the loop ie

for (MyObject& o : oset)  o.value += 1000;

but I had the following error :

error: invalid initialization of reference of type 'MyObject&' from expression of type 'const MyObject'

You can use a reference to assign to the object - by making the loop variable o a reference:

cout << "Increnting ... " << endl;
for (MyObject &o : oset)  o.value += 1000;

* EDIT *

But because set is an ordered container, it returns const iterators. So if you need to amend a set perhaps you should consider using another container instead. Perhaps a map?

But if you really have to change a set - don't do like the code below. It makes valgrind very upset, as Matthieu has pointed out. Why you can't go around erasing elements in a container whilst iterating through that container. Such madness leads to Undefined Behavior.

// ===== ATTENTION - NASTY CODE ALERT !! ========**
    for (MyObject &o : oset)
    {
        MyObject oNew = o;
        oNew.value += 1000;
        oset.erase(o);
        oset.insert(oNew);
    }
// =============================================**

A safer solution

would be to create another set, copy the original set (whilst applying the required changes), then swap the new set to the old one.

Here transform applies the lambda function over the entire set, and stores the results in the new nset set. inserter creates an insertion iterator for the nset container, inserting from nset.begin(). The final transform parameter - the lambda - takes an original set object and adds 1000 to it, returning the new object. Once the transform creates the new set, swap then places the new objects in the original container.

set<MyObject> nset;
transform(oset.begin(), oset.end(), inserter(nset, nset.begin()),
   [](const MyObject &o) { return MyObject (o.value+1000); });
oset.swap(nset);

See this question on set updates for more info.

The incorrect line is for (MyObject o : oset) o.value += 1000; You should use for (MyObject& o : oset) o.value += 1000; instead

Use a reference variable to refer to the object itself:

for (MyObject& o : oset)  
   o.value += 1000;

Note the '&', which makes the name "o" refer to the object in the collection instead of a copy.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM