Evan Ward
Evan Ward

Reputation: 1429

c++ vector without pointers

I have a std::vector<Enemy*> enemies and when i create a new Enemy i do enemies.push_back(this). This works for what i'm doing, but i'm wondering if there is a way to have the vector not need pointers to the enemies. I've tried just std::vector<Enemy> but it seems to cause a bunch of problems i can't figure out.

ie. - had to change to enemies.push_back(*this), and it caused some linker errors for some reason.

looping through with the following works, but removing the pointers creates issues:

void Enemy::updateEnemies(sf::RenderWindow &window) {
    for(std::vector<Enemy*>::iterator it = enemies.begin(); it < enemies.end(); it++) {
        (*it)->update(window);
    }
}

Wondering if someone could point me in the direction of using a vector without pointers and loop through it, updating the objects inside it with the same function..

Upvotes: 3

Views: 1467

Answers (4)

Tunichtgut
Tunichtgut

Reputation: 311

If you want a well-founded answer you have to go one step back - to the design. As far as i see the Enemy objects are registering themselves via constructor in a kind of global list. So far so good.

But how these object are produced ? In this example in Enemy can be construct everywhere

  • On the stack as local
  • By new (smartpointer)
  • As an element in a vector, list, deque ...
  • ...

Depending on this, the Enemy-object cant do any reliable statements about its storage. But one thing that all objects share and what is always accessible from inside the object - is the address ('this'). So the solution via pointer is the only solution that is universal for all constructed Enemy-objects. (References are in this context also "a kind of pointer", and other more complicate constructs may also rely on pointers).

A std::vector cant work because you get 2 different objects. To change one of them does not effect the other. That wont work. And it is not possible to switch to smart pointers without changing your design.

There are 2 common solutions:

Managing the listing from 'inside' Enemy in a generally way like in the given example. Expandable with more features like auto delete entries via destructor.....

Managing the listing from 'outside'. Enemy-objects dont register themselves, the generator of the Enemy-objects is responsible for to do it proper (the design pattern 'factory' is a good solution in this case). And you can use smart pointers what is a good idea in most cases.

There are also some more solutions (including some good solutions) unmentioned, they exceed the scope of the question.

Upvotes: 1

jez
jez

Reputation: 15349

My practice is to make sure the class implements (and makes public) all of the following: a default constructor, a copy constructor, an assignment operator, and a destructor. Then there's no problem with things like std::vector<Enemy>. (The default constructor isn't even necessary for vectors, but can be handy for various other things.)

One caveat, though: when you push_back() an Enemy into your vector, it's actually a copy of the original instance that gets stored. This raises a few gotchas: first, changes to the original (e.g. to the member data of the variable e in the main() scope of my example below) will not affect the copy stored in the vector. If you're accustomed to working with vectors-of-pointers, this may be surprising. Second, the implicit construction of copies and the copying of member data will take time, which may or may not have a significant effect on performance. Third, implicit construction and copying may trigger side-effects (you should simply avoid these in your design, wherever possible).

Here's an example:

#include <vector>
#include <iostream>

class Enemy
{
  public:
    Enemy() : mEvilness( 1 ) {}    // default (i.e. parameterless) constructor
    Enemy( const Enemy& other ) { Copy( other ); } // copy constructor
    Enemy& operator=( const Enemy& other ) { return Copy( other ); } // assignment operator
    ~Enemy() {}  // destructor

    Enemy( int evilness ) : mEvilness( evilness ) {}  // standard constructor

    void Gloat( void ) const
    {
        std::cout << "muahaha";
        for( int i = 0; i < mEvilness; i++ ) std::cout << "ha";
        std::cout << "!\n";
    }

    int mEvilness;

  private:

    // common helper method for copy constructor and assignment operator:
    Enemy& Copy( const Enemy& other )
    {
        mEvilness = other.mEvilness; // make copies of any other member data here
        return *this;
    } 
};


typedef std::vector< Enemy > EnemyHorde;

void AllGloat1( const EnemyHorde& h ) // doesn't matter if you pass by reference...
{
    for( EnemyHorde::const_iterator it = h.begin(); it != h.end(); it++ )
        it->Gloat();
}

void AllGloat2( EnemyHorde h ) // ...or value (but this will take more CPU time)
{
    for( EnemyHorde::const_iterator it = h.begin(); it != h.end(); it++ )
        it->Gloat();
}

int main( int argc, const char* argv[] )
{
    EnemyHorde h;
    Enemy e( 1 );
    h.push_back( e );
    h.push_back( Enemy( 2 ) ); // even transient Enemy instances get copied and stored
    h.push_back( Enemy( 3 ) );

    AllGloat1( h );
    std::cout << "\n";

    e.mEvilness++;  // this will have no effect on the output because h contains a *copy* of e

    AllGloat2( h );

    return 0;
}

Upvotes: 1

pm100
pm100

Reputation: 50190

everbody is saying it in comments - but its the answer

use smart pointers

if you dont know how to do that then google

You life is much easier, safer, faster,...

And they are really easy to use - the main trick with using them is to trust them and dont try to get clever or 'help' them

Upvotes: 2

Thomas Matthews
Thomas Matthews

Reputation: 57708

You don't need pointers (most of the time) in C++.

A copy will be made when using the std::vector::push_back() method.

Example:

Enemy evil_wizard;
std::vector<Enemy> enemy_container;
enemy_container.push_back(evil_wizard);
Enemy Evil_knight;
enemy_container.push_back(Evil_knight);
Enemy pink_elephant;
enemy_container.push_back(pink_elephant);

Upvotes: 0

Related Questions