Reputation: 3351
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
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:
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.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
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
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
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