Search code examples
c++dictionaryshared-ptr

C++ How to add objects to maps and return reference to new created object inside map


Bellow I provide the complete code for something really simple which I'm struggling with..

  • I need to create a map with strings and Objects...
  • When requested, if the string is inside the map, I need to return a reference to one object inside the map
  • When the string is not inside the map, I need to create that object, with that string and return (as before) the reference to the new created object

Please check below the two comments I say "ERROR" to see where the problem is.

My questions are:

  1. How can I insert to a map, one object? what is wrong with the line on the InitObj()?
  2. How can I create return a reference to an object which I've just created on a map? As seen at the end of getHouse() function

Thanks in advance :)

#include <map>
#include <string>
#include <memory>

class House
{
public:

    House(const char* name) : _name(name) {};
    ~House() {};

    std::string getHouseName () { return _name; }
private:
    std::string _name;

    House(const House& copy)
    {
    }
    House& operator=(const House& assign)
    {
    }
};

class Obj
{
public:
    Obj()
    {
        InitObj();
    }
    ~Obj() {};

    House& getHouse (const char *houseName)
    {
        std::string name = houseName;
        auto i = _myHouseMap.find(name);

        //this string doesn't exist on map? then create a new house and add to the map and return the reference to it
        if (i == _myHouseMap.end())
        {
            //create a new house
            House h(houseName); 

            //add to the map
            _myHouseMap.insert(std::pair<const std::string, House>(houseName, h));

            //return the reference to the house created
            return h; //<--- ERROR!!!! need to return the reference!
        }
        return (i->second);
    }



private:
    Obj(const Obj& copy);
    Obj& operator=(const Obj& assign);

    typedef std::map<const std::string, House> myHouseMap;

    myHouseMap _myHouseMap;

    //the initialization will add one object to my map
    void InitObj()
    {
        House h("apartment");
        _myHouseMap.insert(std::pair<const std::string, House>("apartment", h)); //<--- ERROR see reference to function template instantiation 'std::pair<_Ty1,_Ty2>::pair<const char(&)[10],House&>
    }
};


int main(void)
{
    Obj aaa;

    House& myHouse1 = aaa.getHouse ("apartment");
    std::cout << "House is " << myHouse1.getHouseName ();

    House& myHouse2 = aaa.getHouse ("newHouse"); //here a new house must be created and added to the map
    std::cout << "House is " << myHouse2.getHouseName ();

    return 0;
}

Solution

  • For your first question, you made House noncopyable (your copy constructor and copy assignment operator are private). The approach you are taking to insert requires you to make a pair first, the construction of which will copy the House you pass in. If you have access to a C++11 compiler, you can still have the value-type of your map be House and just use emplace instead:

    void InitObj()
    {
        _myHouseMap.emplace(std::piecewise_construct,
                            std::forward_as_tuple("apartment"),  //key
                            std::forward_as_tuple("apartment")); //value
    }    
    

    If you don't have access to a C++11 compiler, you will have to change the value type to be House* or some equivalent copy-constructible type.

    For the second question, std::map::insert (and emplace) return a pair<iterator, bool>. Just take advantage of that:

    if (i == _myHouseMap.end())
    {
        House h(houseName); 
    
        // we insert our new house into the map
        // insert() will return a pair<iterator, bool>. 
        // the bool will be true if the insert succeeded - which we know
        // it will because we know that this key isn't already in the map
        // so we just reassign 'i' to be insert().first, the new iterator
        // pointing to the newly inserted element
        i = _myHouseMap.insert(std::pair<const std::string, House>(houseName, h)).first;
    }
    
    // here i either points to the element that was already in the map
    // or the new element that we just inserted. either way, 
    // we want the same thing
    return i->second;