My naming convention is 'g_' for variables global to a class, 'm_' for variables which are members of a function, 'p_' for parameters. Old habits and all that. I'd left out the mMeshChannels (among other bits) intentionally to keep the problem simple
Not to nitpick too much, but... that's weird. Also, the terminology is all wrong. "Member" has a specific definition in C++ and local variables are _not_ members. Globals likewise are pretty universally considered to be a bit bigger than just "to a class." To each his own, but I'd argue that there's very real value to using more common conventions, especially when interfacing with other human beings.
I try to avoid using 'new' wherever possible (if I can let C++ deal with it, all the better),
That's good practice. That's usually called a preference for values rather than references.
I've never actually had to use smart pointers before, but the bit of reading I did around them suggests that the data in them gets destroyed when the variable goes out of scope, which is why I stuck with 'new'.
That's partly true. A smart pointer cleans up after itself. A modern smart pointer can easily deal with transfer of ownership, though (pre-C++11, the language made automatic transfer of ownership awkward and error-prone).
Consider:
std::unique_ptr<Foo> make_foo() {
std::unique_ptr<Foo> local = std::make_unique<Foo>();
// do stuff with `local`
stuff(*local);
// this implicitly transfers ownership from `local` to the caller via the return value
return local;
// `local` goes out of scope and is destroyed
// however, it no longer owns the instance of Foo (the caller does) so that instance is _not_ destroyed!
}
void sink(std::unique_ptr<Foo> ptr) {
// do stuff with `result`
other_stuff(*ptr);
// `ptr` is going out of scope and it owns the Foo object
// so _now_ the Foo instance is destroyed! (we could save it off
// to a member variable or give it to another function or
// return it back to the caller, if we wanted to)
}
int main() {
// acquires ownership from the callee
std::unique_ptr<Foo> result = make_foo();
// do stuff with `result`
other_other_stuff(*result);
// transfer ownership to another function - must explicitly state that you're releasing ownership via std::move
sink(std::move(result));
// `result` goes out of scope and is destroyed
// however, it no longer owns the instance of Foo (we gave it to `sink`) so the object is not double destroyed!
}
The smart pointer will only destroy its contained value when it semantically should. In the example above, the make_foo function cleanly returns an owned pointer. It is not accidentally deleted. It is not dangling. In fact, unique_ptr makes it impossible (well, moderately difficult) to have a dangling pointer or to forget to delete an object but makes it easy to pass around ownership of the object, which is exactly what you want. The only difference with a regular pointer is that you might have to call .get() or std::move() here and there when the compiler isn't sure how you want to affect ownership (and since you have to be explicit that is also good as now your code is easier to read and reason about!).
You can have std::unique_ptr as a local variable, a return value, a parameter, a type member, a global, you can put it in a std::vector or in any other standard container, etc. In the vast majority of cases, it Just Works(tm) with no surprises.
Use C++ references (`Foo&` or `Foo const&`) when you want a non-nullable non-owning reference to an object. Use raw pointers (`Foo*` or `Foo const*`) when you want a nullable non-owning reference to an object. Use a unique pointer (`std::unique_ptr<Foo>`) when you want a nullable owning reference to an object. Use a regular value (`Foo`) when you want a non-nullable non-owning copy of an object. There's a few other smart pointers in the standard for more specialized cases and you can of course write your own or find others in existing libraries for even more specialized cases.
std::unique_ptr has also been built to deal with older APIs and C APIs that require a special deleter (rather than just calling `delete`), e.g. for SDL2 objects I frequently use code like:
auto window = std::unique_ptr<SDL_Window, void(*)(SDL_Window*)>(SDL_CreateWindow(/*...*/), &SDL_DestroyWindow);
Unfortunately, make_unique doesn't handle deleters resulting in the overly verbose ugly syntax above. You can write a helper like this, though:
// so you don't have to spell out the deleter's type when passing parameters or the like
template <typename T>
using scoped_resource = std::unique_ptr<T, void(*)(T*)>;
// acquire ownership of a pointer and assigns the specified deleter
template <typename T, typename D>
scoped_resource<T> make_scoped(T* ptr, D&& deleter) {
return {ptr, std::forward<D>(deleter)};
}
// example
auto window = make_scoped(SDL_CreateWindow(/*...*/), &SDL_DestroyWindow);
So now you have a unique_ptr that references a window and will automatically call the right function when/if it is responsible for deleting the object. It's not quite the perfect interface which is why a distinct scoped_resource has been proposed for standardization a few times, but it works surprisingly well.
... and wow did I just get off topic.