Rein
Rein

Reputation: 3351

Retrieve object instance from map

I'm new to C++ and I'm trying to make a little game. I have this class named "UnitController" which stores multiple instances of the class "Unit" in a map. The class also has a method "getUnit" which should return one of the stored units.

It seems this method is only partially working. I think I get a copy of the unit instead of the requested instance.

Could anyone point me int the right direction?

#include "UnitController.h"
#include "Unit.h"

using namespace ci;
using std::map;

UnitController::UnitController()
{
}

void UnitController::addUnit( Vec2f position )
{
    Unit mUnit = Unit();
    mUnit.setup( position );
    Units.insert( std::pair<int,Unit>( Units.size()+1, mUnit ) );
}

Unit UnitController::getUnit( int k )
{
    Unit selectedUnit = Units[0];
    return selectedUnit;
}

void UnitController::update()
{
    for( map<int,Unit>::iterator u = Units.begin(); u != Units.end(); ++u ){
        u->second.update();
    }
}

void UnitController::draw()
{
    for( map<int,Unit>::iterator u = Units.begin(); u != Units.end(); ++u ){
        u->second.draw();
    }
}

Upvotes: 1

Views: 214

Answers (4)

hmjd
hmjd

Reputation: 121961

The method:

Unit UnitController::getUnit( int k )
{
    Unit selectedUnit = Units[0];
    return selectedUnit;
}

is returning a, possibly default, copy of the element with index 0 (do you mean to ignore k?). If you wish to avoid a copy being returned then return a reference instead to the element at index 0, not to the local variable selectedUnit:

Unit& UnitController::getUnit( int k )
{
    return Units[k];
}

If the entry keyed by k is removed from the map then a caller that has a reference to the Unit of the entry now has a dangling reference, use of which is undefined behaviour. There a few things to consider to try and avoid this:

  1. Does a client of UnitController require direct access to a Unit in the map? If not and the client only requires to update certain attributes of a Unit then modify the UnitController interface to support updates to Unit without providing a client direct access.
  2. If a client does require direct access then consider using a std::shared_ptr<Unit> instead of a Unit for entry value type (and don't return by reference in this case). This would address the dangling reference problem but what does it mean for caller to have access to a Unit that is no longer in the UnitController?

operator[] will create an entry in the map for a key that does not currently exist:

Inserts a new element to the container using key as the key and a default constructed mapped value and returns a reference to the newly constructed mapped value. If an element with key key already exists, no insertion is performed and a reference to its mapped value is returned.

If you wish to not have this behaviour then use find() and decide on what action to take if an entry for key k does not exist.

Upvotes: 3

Zdeslav Vojkovic
Zdeslav Vojkovic

Reputation: 14581

of course you get the copy. consider this:

void UnitController::addUnit( Vec2f position )
{
    Unit mUnit = Unit();     // you create local instance of Unit
    mUnit.setup( position );
    // you add it to the map - actually the copy is created and stored in map
    Units.insert( std::pair<int,Unit>( Units.size()+1, mUnit ) );
    // mUnit is destroyed after the function exits
    // your map would now contain a destroyed object if it wasn't a copy
}

Besides your getUnit method also returns a copy. this might be OK or not, depending on what the client does with it. if it changes the state of returned unit instance, then it will not be reflected in the copies withing the map.

You might want to use map<int,Unit*> but then you need to take care of lifetime of the original objects. You can't destroy it because pointer will point to destroyed object. Therefore, a proper solution is to use shared_ptr<Unit>

However, there are other problems with your design. You store the current size + 1 as the map key, which means that you use it as an index. Why not just use std::vector<shared_ptr<Unit>> instead?

Upvotes: 0

Mr.C64
Mr.C64

Reputation: 42924

In this code:

Unit UnitController::getUnit( int k )
{
    Unit selectedUnit = Units[0];
    return selectedUnit;
}

you return Unit by value, so you actually get a copy of the original Unit.

(Note also that you seem to have a bug, since you use 0 as key, instead of parameter k...)

If you want to modify the original Unit (i.e. the one stored in the map), you can return by reference (Unit &):

Unit& UnitController::getUnit(int key)
{
    return Units[k];
}

As a side note, you can simplify your insertion code. Instead of using std::map::insert() method:

Units.insert( std::pair<int,Unit>( Units.size()+1, mUnit ) );

you can just use std::map::operator[] overload:

Units[ ...the key here... ] = mUnit;

Upvotes: 2

Bathsheba
Bathsheba

Reputation: 234655

You will indeed receive a deep copy of Unit.

Consider creating a reference counting pointer (aka smart pointer) encapsulating a Unit and set that to the value type of the map.

Upvotes: 0

Related Questions