简体   繁体   中英

How do I change where the pointer is pointing to through a return statement?

I have a Player object which holds an ID string. I'm trying to check if a player already exists by iterating through a list of Players. If the player already exists, I want to return the existing player, else I will make a new instance of the Player object.

Player *playerExists(const std::string &playerId, const std::vector<Player>* players)
{
    for (Player existingPlayer : *players)
    {
        if (existingPlayer.playerId == playerId)
        {
            std::cout << "Player " << existingPlayer.name << " exists" << std::endl;
            return &existingPlayer;
        }
    }
    return new Player();
}

The problem seems to be in the return statement. I don't know how to return the specific object to a pointer. This seems to be the only way I found not to get an error (talking about the & sing in the return statement).

Player* player = playerExists("SomeID", listOfPlayers);
listOfPlayers->push_back(*player);
delete player;

I'm quite new to using raw pointers so I probably just don't understand what the problem here is. I'd really appreciate if someone could explain what I'm doing wrong.

There are several issues with this code.

Each iteration of the loop is making a local copy of a Player in the vector, and thus will return a pointer to that copy if a match is found. But when the current iteration is finished, that copy is destroyed, so you will end up returning a dangling pointer . To fix that, your loop need to take a reference to each Player in the vector:

for (Player &existingPlayer: *players)

The next issue is if no match is found, you return a new ed Player . The problem with that is the caller is then unconditionally delete ing the returned Player* regardless of whether it was new ed or not. The vector owns the Player objects it is holding, so delete ing one of those objects is undefined behavior .

A better design choice is to have playerExists() return nullptr if no match is found. The caller can then do different things depending on whether a Player or a nullptr is returned, eg:

Player* playerExists(const std::string &playerId, const std::vector<Player>* players)
{
    for (Player &existingPlayer : *players)
    {
        if (existingPlayer.playerId == playerId)
        {
            std::cout << "Player " << existingPlayer.name << " exists" << std::endl;
            return &existingPlayer;
        }
    }
    return nullptr;
}
Player* player = playerExists("SomeID", listOfPlayers);
if (player) {
    // ID found, use player as needed...
    listOfPlayers->push_back(*player); // why re-add a player that is already in the list???
} else {
    // ID not found, do something else...
    listOfPlayers->push_back(Player{}); // why add a blank player???
}

Different ways of tackling this. Biggest problem might be in this line:

for (Player existingPlayer : *players)

It's making a copy (I think) of a player already in the collection.

When you do a return &existingPlayer; ... well it's returning an object on the stack. Bad things will happen.

You are better off iterating the collection and returning an iterator, IMHO... Maybe...

std::vector<Player>::const_iterator playerExists(const std::string &playerId, const std::vector<Player>& players)
{
    std::vector<Player>::const_iterator it = players.begin();
    for (; it != players.end(); ++it)
    {
        if (it->playerId == playerId)
        {
            std::cout << "Player " << it->name << " exists" << std::endl;
            break;
        }
    }
    return it;
}

You can always test after the return for

With the returned iterator, you'll have to do something with it...

auto it = playerExists(playerId, players);
if (it == players.end())
{
   // not found
} else {
   // found
}

Your problem is there:

for (Player existingPlayer : *players)

In each iteration of the for loop, existingPlayer is a copy of the player that is stored in a local variable in the scope of the for loop. You are returning a pointer to a local variable which get destroyed after each loop iteration.

To avoid the copy, use a reference :

for (Player &existingPlayer : *players)

Here, existingPlayer is a reference to the real player. This is what you did with the const std::string in the function prototype.

I have also some suggestions to possibly improve your code:

  • Avoid raw C pointers , C++ give you tools to get the same functionnality in a (much) less error prone way and hence, more secure. If you don't need NULL as I see in your code, a reference is probably better. Else you should consider smart pointers ( std::unique_ptr , std::shared_ptr . I would pass players by reference instead of pointers in your case.

  • std::vector may not be the best container. Choosing the right container can be difficult. Currently you are iterating over the whole array to find the player by his id. You should use a std::unordered_map<std::string,Player> that use a hash table and probably speed up lookup time. I would also use a typedef of std::a_container_class<Player> to avoid massive code rewrite if you change the container kind or write one from scratch later.

  • Does copying a Player make sense? If not, explicitly delete copy constructor and copy assignment operator , the compiler will then throw an error if you want to copy the Player . Note that this is a design choice that I often do, multiple instance of the same player is normal depending on your objects design.

  • I'm doubtful about return new Player(); . Why do you return a new player if no player exist? With my personal interpretation (might be totally wrong), I would return an iterator. No pointer involved, you can "dereference" the iterator to get a reference to the found player and the non existence of player is signaled by return an iterator equal to players.end() . But beware a big drawback, be sure to test before modifying the container and invalidating iterators.

With my own design choices , I made a slightly untested and shorter version of your function:

#include <unordered_map>
typedef std::unordered_map<std::string,Player> PlayerCollection;
/* ... */
PlayerCollection::const_iterator playerExists(const std::string &playerId, const PlayerCollection &players)
{
    return players.find(playerId);
}

Keep in mind that my suggestions are opinionated and are not the absolute truth. Read the other answers which have different ideas.

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