Rick
Rick

Reputation: 7506

C++ map, use const reference as value type, what is the problem here?

Today I saw my boss's code which uses a const reference as a map's value type.

Here's the code:

class ConfigManager{
    public:
        map<PB::Point, const PB::WorldPoint&, compare_point> world_point;
    //the rest are omitted
};

(PB is Google Protobuf, we are using the Protobuf library. I don't know much about it or if it's relevant to the question. )

What this class does is that it reads some config files and put it into some maps for searhing.


At first I was surprised because I haven't seen a map with a reference as its value, which is e.g. map<int, classA&> aMap.

So then I searched on SO and these 2 questions tell me that I can't do that.

C++: Is it possible to use a reference as the value in a map?

STL map containing references does not compile

Then I tried this code, indeed it doesn't compile:

Code Example1

struct A {
    int x = 3;
    int y = 4;
};

map<int, A&> myMap;

int main() {
    A a;
    myMap.insert(make_pair(1, a));
}

But if I change map<int, A&> myMap; to map<int, const A&> myMap;, it compiles.

Yet another problem occured. With map<int, const A&> myMap;, I can't use [] to get the pair, but I can use map.find().

(My boss told me to use map.find() after I told him using[] can't compile).

Code Example2

struct A {
    int x = 3;
    int y = 4;
};

map<int, const A&> myMap;

int main() {
    A a;
    myMap.insert(make_pair(1, a));

    //can't compile
    cout << myMap[1].x << " " << myMap[1].y << endl; 
    
    //can work
    //auto it = myMap.find(1);
    //cout << it->second.x << " " << it->second.y << endl;
}

So till here I was thinking my boss was correct. His code was correct.


The last story is that I showed the code to some online friends. And they noticed a problem.

Code Example3

#include <map>
#include <iostream>
#include <string>
using namespace std;

struct A {
    int x = 3;
    int y = 4;
    ~A(){
        cout << "~A():" << x << endl;
        x = 0;
        y = 0;
    }
};

map<string, const A&> myMap;

int main() {
    A a;
    cout << a.x << " " << a.y << endl;
    myMap.insert(make_pair("love", a));
    a.x = 999;
    cout << "hello" << endl;
    auto s = myMap.find("love");
    cout << s->second.x << " " << s->second.y << endl;
}

The output is:

3 4
~A():3
hello
0 0
~A():999

If I understand the output correctly(correct me if I get it wrong), it indicates that:

  1. make_pair("love", a) creates an object pair<"love", temproray copy of a>. And the pair gets inserted into myMap.
  2. Somehow, I don't know how it happens, the temporary copy of a gets destructed immediately. To me, it means the memory of the temporary copy of a is now not owned by anyone and it is now a free space of memory that can be filled with any values, if I understand correctly.

So now I am getting confused again.

My questions are:

  1. What happens to the Code Example3? Is my understanding correct? Why does temporary copy of a get destructed right after the statement? Isn't using a const reference can extend a temporary's lifetime? I mean, I think the it should not get destructed till main finishes.

  2. Is my boss's code incorrect and very dangerous?

Upvotes: 2

Views: 2045

Answers (3)

Fran&#231;ois Andrieux
Fran&#231;ois Andrieux

Reputation: 29023

What happens to the Code Example3? Is my understanding correct?

Your explanation is close. The std::pair that is returned by std::make_pair is the temporary object. The temporary std::pair contains the copy of a. At the end of the expression the pair is destroyed, which also destroys its elements including the copy of a.

Why does temporary copy of a get destructed right after the statement? Isn't using a const reference can extend a temporary's lifetime? I mean, I think the it should not get destructed till main finishes.

The temporary here is the result of std::make_pair which is being used as an argument to the member function insert. The relevant rules that apply here are :

Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference, with the following exceptions:

  • [...]
  • a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call [...]
  • [...]

source

The full expression containing the function call is the expression myMap.insert(make_pair(1, a)); This means that the lifetime of the result of std::make_pair ends after the function return, including the A it contains. The new std::map element will refer to the A in the temporary std::pair which will become dangling once insert returns.

Is my boss's code incorrect and very dangerous?

Yes, myMap contains a dangling references.

Upvotes: 1

Red.Wave
Red.Wave

Reputation: 4201

You may use std:: unique_ptr<A> instead. Then emplace instead of insert:

using value_t=std:: unique_ptr<A>;
std::map<int, value_t> myMap;
myMap.emplace(1,new A);
myMap[1]=new A{5,6};
myMap[1]->x=7;

more on std:: unique_ptr<A>: https://en.cppreference.com/w/cpp/memory/unique_ptr

Upvotes: 1

john
john

Reputation: 87959

Why does temporary copy of a get destructed right after the statement?

Because (in most cases) that's how temporaries work. The live until the end of the statement in which they are created. The extension to a temporaries lifetime doesn't apply in this case, see here. The TLDR version is

In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime.

can I use const reference as a map's value type?

Yes as long as you realise that adding a const reference to a map has no effect on the lifetime of the object being referred to. Your bosses code is also incorrect because the temporary returned by make_pair is destroyed at the end of the statement.

Upvotes: 2

Related Questions