Reputation: 4505
My current environment is Visual Studio, though my project is cross platform I'd prefer to figure something out for a windows platform because setting it up on another operating system would require a day or two of work gathering external libraries and dependencies and setting up the project.
I've been using C++11 features for some time now and am usually pretty good about object ownership (I haven't encountered many situations where the ownership could become cyclic.)
Unfortunately I am currently trying to track down the origin (line number, shared_ptr creation id, or owning objects) of shared_ptr copies for a given object which I would expect should be released but which exists in a cycle somehow.
I am, somewhere, breaking the ownership contract (possibly in a lambda which captures a shared_ptr and extends its lifetime).
I've looked on stack overflow and online and the common refrain is "revisit your entire design" which is basically a non-answer (and the reason I am posting this in the first place). I acknowledge my design has a problem similar to a memory leak (which tools exist to detect), I acknowledge it is my fault this issue exists. I simply want to narrow down where to look in a reasonable way.
It seems if I could somehow access the shared_ptr constructor and destructor I could print debug information via some kind of internal guid which might help narrow down the exact problem (if I know shared_ptr with an id of 30 is created but the destructor is not hit I can, on a second run-through, place a breakpoint on the precise place where a shared_ptr with that id is created, this would be a language level debug tool). The other option, I suppose, would be if some kind of memory analyser could determine the origin line number of every alive instance of a particular shared_ptr.
I also inherit from enable_shared_from_this if that matters.
Any advice for tracking down the issue?
Upvotes: 1
Views: 1135
Reputation: 304
Well subclassing shared_ptr is a bad idea because shared_ptr does not have a virtual destructor. I assume that you know about RAII and all that jazz so the best way that I would recommend tracking it down would be to wrap your shared_ptr in a struct or a class. Something like:
template<typename T>
struct shared_ptr_wrapper{
shared_ptr<T> ptr;
int line;
shared_ptr_wrapper<T>(T& obj, const int selected_line)
{
//Initialization as normal. Maybe even print out the address of the pointer
}
//Relevant access functions like constructing, destructing
//and changing references
};
***Interestingly enough if you want to programatically add some information to this class c++ offers the compile time constant __LINE__ which points to the line number from the line which it is called that could make your debugging festival easier than setting breakpoints as the objects themselves would have this information.
You wont have the syntactic sugar that you could have with a normal shared_ptr but this will do better in terms of debugging. You could organize the constructors virtually to provide a suitable subclassing environment if need be and for the following reason
virtual ~shared_ptr_wrapper()
{
std::cout << line;
std::cout << ptr//May want to print the address of the shared pointer
//So that you can stop execution and take a look at the variables right before
//It is/Is not destructing
}
Also asserts can be used rather effectively depending on your specific program. If you believe a variable should be dead at a certain point, call a method that asserts that and if you don't then you have your answer, a debugging tool I have for that is fassert, A slightly upgraded version of assert telling you the line number
#define fassert(expr) lassert((expr), #expr, __FILE__, __LINE__) //This macro has been
//tested by the coding gods greater than me.
bool lassert(bool invariant, std::string statement, std::string file, int line)
{
if(!invariant)
{
std::cerr << "fassert Failed!" << std::endl;
std::cerr << "Statement: " << statement.c_str() << std::endl;
std::cerr << "File: " << file.c_str() << std::endl;
std::cerr << "Line: " << line <<std:: endl;
abort();
}
return true;
}
Now you mentioned lambdas capturing variables. A simple solution that objective c programmers use is to make weak references (I am a little fuzzy on Boosts' documentation on how to do that) and use them in all of your lambdas instead. This may mean that you need to subclass again and delete the variables manually; that is, since you are simply wrapping instead of subclassing, you will have to perform all the checks every so often in your programming loop or have each of the pointers do with themselves after some time interval (Multi-threading maybe?)
And lastly, what the reference "revisit your entire design" strictly means not to change your entire program, but it means to avoid using lambdas when static function calls will do, or using c-style function pointers to keep the amount of "captured" variables to a minimum.
There are numerous other memory tricks, but most likely if you keep programming the exact same way, some other cyclical reference will appear later down the line. And for this reason I suggest not using a memory analyzer because it will definitely slow your program down, c++ is known for its speed, and cause needless amounts of setting up and monitoring so long as you are not debugging a 20 source file project with multiple instances. From experience, they can get cumbersome. As programmers we all just want our code running and not taking up our entire RAM. There is no silver bullet, try a couple of things or combinations and see what works out for you. Best of luck in your debugging endeavors,
-BV
Upvotes: 2