2

Working on a game in Cocos2d-x. I have CCLayers* and lots of CCSprites* that are created. I add these CCSprites in a std::vector after I create them.

My concern is memory and deleting.

I am trying to wrap my head around std::unique_ptr. My understanding is that smart pointers will help and clean up memory and prevent leaks.

But I dont understand how to use it. Do I make a unique_ptr out of every CCSPrite*? Do I make a unique_ptr and put my whole vector in it?

Can anyone help me understand and give me an idea what to brush up on?

Xeo
  • 129,499
  • 52
  • 291
  • 397
Jasmine
  • 15,375
  • 10
  • 30
  • 48
  • 1
    Cocos2D-x uses a custom garbage collector, you shouldn't use smart pointers (or RAII for that matter) unless you like deleting stuff twice. – Luke B. Jun 23 '13 at 23:04

4 Answers4

2

Wherever you use new currently, make sure the result is immediately go to ctor of a unique_ptr, or its reset() function. And that smart pointer is placed so it will live where needed. Or you may pass the controlled object ahead to a different instance. Or nuke it using reset().

Vectors you don't usually allocate with new, so they are not subject to smart pointering: the vector itself manages the memory for the content, you're ahead by that.

Balog Pal
  • 16,195
  • 2
  • 23
  • 37
  • 1
    Ah, so you are saying put the resulting `new` in a unique_ptr ctor and then place the unique_ptr in my vector so I can keep track of these objects. – Jasmine Jun 23 '13 at 20:27
  • no, you use smart pointer where you NOT use vector. in vector you just put the objects without new or anything. please read up on `value semantics` and how STL works before starting to use it – Balog Pal Jun 23 '13 at 20:29
  • 2
    There's nothing wrong with putting smart pointers in a vector. The choices, of whether to use a vector or not, and whether to use a smart pointer or not, are completely orthogonal to each other. The one has not a smidgen of influence on the other. – Benjamin Lindley Jun 23 '13 at 20:59
  • @BenjaminLindley: yeah, provided you have a hierarchy and want a polymorphic collection, let me bet 99:1 the OP is not in this situation – Balog Pal Jun 23 '13 at 21:00
  • Okay, but when you say *"you use smart pointer where you NOT use vector"*, that indicates that the choice of using a vector somehow precludes the use of smart pointers. Why would it? – Benjamin Lindley Jun 23 '13 at 21:05
  • @BenjaminLindley - that is how I read what Balog wrote exactly. – Jasmine Jun 23 '13 at 21:08
  • the answer stands in the context of the question, not in general. Do you expect all questions to include 200 pages of exceptional situations that hardly apply? -- OTOH please be constructive, submit a better phrased answer :) – Balog Pal Jun 23 '13 at 21:08
  • 1
    @BalogPal I think that what I am doing is very polymorphic. I have some base classes where other's subclass. Those resulting subclasses are what is going in the unique_ptr and then into the vector so I can access each one and do what needs to be done with it – Jasmine Jun 23 '13 at 21:11
  • 1
    Well, next time take care to put important info in the question. And you can look around SO for "duplicates" there are ready answers on how to have a polymorphic STL collection – Balog Pal Jun 23 '13 at 21:13
  • I can hardly answer the question without a solid knowledge of cocos2d, and how it handles object lifetimes. I can, however, point out incorrect details in other answers. That is what the comment section is for. – Benjamin Lindley Jun 23 '13 at 21:15
  • 1
    Can you explain to me how me telling you I had a polymorphic collection would have been important to your answer here? I asked about using a unique_ptr and some confusion on usage if the vector goes in it or the sprites themselves. – Jasmine Jun 23 '13 at 21:19
1

If you need a polymorphic container, that is a vector that can hold CCSprites or any derived class, then you can use a std::vector<std::unique_ptr<CCSprite>> to describe this and provide you with you with lifetime management of the classes.

#include <memory>
#include <vector>
#include <iostream>

using namespace std;

class Foo {
    int m_i;
public:
    Foo(int i_) : m_i(i_) { cout << "Foo " << m_i << " ctor" << endl; }
    ~Foo() { cout << "Foo " << m_i << " ~tor" << endl; }
};

class FooBar : public Foo {
public:
    FooBar(int i_) : Foo(i_) { cout << "FooBar " << m_i << " ctor" << endl; }
    ~FooBar() { cout << "FooBar " << m_i << " ~tor" << endl; }
};

int main(int argc, const char** argv) {
    vector<unique_ptr<Foo>> foos;
    Foo foo(1);
    foos.emplace_back(unique_ptr<Foo>(new Foo(2)));

    cout << "foos size at end: " << foos.size() << endl;

    return 0;
}

(I tried adding an example of a short scoped unique_ptr being added to the vector but it caused my GCC 4.7.3 to crash when testing)

Foo 1 ctor
Foo 2 ctor
foos size at end: 1
[<-- exit happens here]
Foo 1 dtor
Foo 2 dtor

If you don't need a polymorphic container, then you can avoid the memory management overhead by just having the vector directly contain the CCSprite objects. The disadvantage to this approach is that the address of given sprites can change if you add/remove elements. If the object is non-trivial this can quickly get very expensive:

std::vector<CCSprite> sprites;
sprites.emplace_back(/* args */);
CCSprite* const first = &sprites.front();
for (size_t i = 0; i < 128; ++i) {
    sprites.emplace_back(/* args */);
}
assert(first == &sprites.front()); // probably fires.
kfsone
  • 23,617
  • 2
  • 42
  • 74
  • 3
    Please note that `emplace_back` is there to prevent copy-construction (if possible), by means of forwarding its arguments to the contained type's constructor, so you should use `foos.emplace_back(new Foo(2))`. I'm guessing you're using VS, which doesn't (didn't?) correctly implement `emplace_back`. – Tom Knapen Jun 23 '13 at 21:33
  • 1
    If `CCSprite` has an expensive move constructor then `vector` will perform worse then `vector`, as when it grows past certain bounds the elements are move constructed to new storage. – Andrew Tomazos Jun 23 '13 at 21:42
  • ok, let me read this carefully. Right now I have a vector for each CCSprite* type I am using. So a vector and each Fields object in the vector has its own vector that stores the veggies the user has in the field. – Jasmine Jun 23 '13 at 22:04
  • @TomKnapen actually more just a force of habbit - I had emplace in one but not the other. Good catch, edited to match. – kfsone Jun 24 '13 at 01:56
  • @Jason So you if the vectors are the authoritative container for your veggies you can replace `vector` with `vector>` or you can have one big vector of everyone by having `vector>` and it will manage the lifetime of objects you assign to it. E.g. if you do vector.erase(begin()), it will see to the invocation of the dtor of the object it pointed to. – kfsone Jun 24 '13 at 02:00
  • It's generally a good idea to prefer `make_unique` over constructor calls of `std::unique_ptr`, partly because it conveniently deduces the type, and partly because there are situations when it provides better exception safety (although in the specific way used by your code it won't matter). See here (including the links provided there): http://stackoverflow.com/questions/7038357/make-unique-and-perfect-forwarding – jogojapan Jun 24 '13 at 02:04
  • Have you really edited the code as suggested by Tom Knapen? It sill calls `emplace_back` with a `std::unique_ptr` object (rather than with the arguments for the constructor). – jogojapan Jun 24 '13 at 02:06
  • 1
    @jogojapan O:) I have now :) Also, I'd best go read up on make_unique. – kfsone Jun 24 '13 at 02:40
  • 1
    @TomKnapen: Please don't pass a raw pointer into `emplace_back`! If the vector reallocates and the realloc throws an exception (for whatever reason), the memory is leaked, as it isn't owned by a `unique_ptr` yet. Only do `.emplace_back(std::unique_ptr(new Foo(...)));` please. – Xeo Jun 25 '13 at 10:09
  • @Xeo good catch, didn't think about that. I guess this counts for every smart pointer (like) type? – Tom Knapen Jun 25 '13 at 12:35
  • @xeo Rolled back the edit. My compiler doesn't support make_unique, so I'm going to leave as is. But doesn't it suffer from the same problem? if emplace_back's ctor fails, we have a floating pointer. Bring on the make_unique: `emplace_back(make_unique(...));`, correct? – kfsone Jun 25 '13 at 17:33
  • @kfsone: `emplace_back` can only be called when its argument is fully constructed, so the raw pointer *has* to be owned by the (temporary) `unique_ptr`. – Xeo Jun 25 '13 at 19:20
  • @Xeo yes, but `emplace_back(unique_ptr(new Foo(...)))` the new could succeed but the unique_ptr ctor could fail; isn't that why we want make_unique? – kfsone Jun 25 '13 at 21:14
  • 1
    @kfsone: No, `unique_ptr` ctor is guaranteed `noexcept`. We want `make_unique` in the case of *multiple* arguments to, say, `emplace_back`, because the construction of the "sub"arguments (`new Stuff`s) are unsequenced, meaning it could allocate one, then try the next, that could fail and the memory would leak, because the first one wasn't yet bound to the `unique_ptr`. With `make_unique`, the allocation happens inside the function and is as such sequenced. – Xeo Jun 25 '13 at 22:24
1

Simplistically unique_ptr<T> is a wrapper class for a member T* p. In unique_ptr::~unique_ptr it calls delete p. It has a deleted copy constructor so that you don't accidentally copy it (and hence cause a double deletion).

It has a few more features, but that is basically all it is.

If you are writing a performance-critical game, it is probably a better idea to manage memory manually with some sort of memory-pool architecture. That isn't to say that you can't use a vector<unique_ptr<T>> as part of that, just to say that you should plan out the lifetime of your dynamic objects first, and then decide what mechanism to use to delete them at the end of that lifetime.

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
1

Cocos2d-x objects have own reference counter, and they use autorelease pool. If you will use std::unique_ptr, you should manually remove created object from autorelease pool and than register it in unique_ptr. Prefer to use CCPointer: https://github.com/ivzave/cocos2dx-ext/blob/master/CCPointer.h

Sergey Shambir
  • 1,562
  • 12
  • 12
  • Can you explain what you mean by manually remove from autorelease pool? Is that the same as just not doing `->autorelease();` in my code where I am doing it? – Jasmine Jun 25 '13 at 14:37
  • Cocos2d-x calls autorelease in it's create* methods. If you want remove from pool object that was already added, see CCPoolManager class reference. – Sergey Shambir Jun 26 '13 at 20:18