views:

233

answers:

6

Hello, i have small problem understanding why my smart pointer class is leaking on self assing. If i do something like this

SmartPtr sp1(new CSine());//CSine is a class that implements IFunction iterface
sp1=sp1;

my colleagues told me that my smart pointer leaks. I added some log messages in my smart pointer to track what is going on and a test and reported this:

SmartPtr sp1(new CSine());
->CSine constructor
->RefCounter increment 0->1
->RefCounter constructor
->SmartPtr constructor

sp1=sp1;
->checks if this.RefCounter == to parameter.RefCounter, if true returns the smart pointer unmodified else modifies the object and returns it with the new values; in this case it returns true and returns the object unchanged.

at the end
->SmartPtr destructor
->RefCounter decrement 1->0
->RefCounter destructor
->CSine destructor

i can't understand why they consider that my smart pointer leaks...any ideas? Thank you in advance!

class SmartPtr
{
private:
    RefCounter* refCnt;
    void Clear()
    {
     if(!isNull() && refCnt->Decr() == 0)
      delete refCnt;
     refCnt = 0;
    };
public:
    explicit SmartPtr();
    explicit SmartPtr(IFunction *pt):refCnt(new RefCounter(pt)){};
    SmartPtr(SmartPtr& other)
    {
     refCnt = other.refCnt;
     if (!isNull())
      refCnt->Incr();
    };
    virtual ~SmartPtr(void){Clear();};

    SmartPtr& operator=(SmartPtr& other)
    {
     if(other.refCnt != refCnt)
     {
      if(!rVar.isNull())
       other.refCnt->Incr();
      Clear();
      refCnt = other.refCnt;
     }
     return *this;
    };

    SmartPtr& operator=(IFunction* _p)
    {

     if(!isNull())
     {
      Clear();
     }
     refCnt = new RefCounter(fct);
     return *this;
    };

    IFunction* operator->();
    const IFunction* operator->() const;
    IFunction& operator*();
    const IFunction& operator*() const;
    bool isNull() const { return refCnt == 0; };

    inline bool operator==(const int _number) const;
    inline bool operator!=(const int _number) const;
    inline bool operator==(IFunction* _other) const;
    inline bool operator!=(IFunction* _other) const;
    inline bool operator==(SmartPtr& _other) const;
    inline bool operator!=(SmartPtr& _other) const;
};

class RefCounter
{
    friend class SmartPtr;
private:
    IFunction* p;
    unsigned c;

    explicit RefCounter(IFunction* _p):c(0),p(_p)
    {
     if(_p != NULL)
      Incr();
     cout<<"RefCounter constructor."<<endl;
    }
    virtual ~RefCounter(void)
    { 
     cout<<"RefCounter destructor."<<endl;
     if(c == 0)
      delete p; 
    }
    unsigned  Incr()
    {
     ++c;
     cout<<"RefCounter increment count:"<<c-1<<" to "<<c<<endl;
     return c; 
    }
    unsigned  Decr()
    {
     if(c!=0)
     {
      --c;
      cout<<"RefCounter decrement count:"<<c+1<<" to "<<c<<endl;
      return c;
     }
     else
      return 0;
    }
};
+3  A: 
SmartPtr& operator=(SmartPtr& other)
    {
        if(rVar.refCnt != refCnt)

should be:

    if ( this != & other )
anon
the output is the same in my test, so why it leaks? when i receive a SmartPtr if is not the same i increase the count, release the old RefCount and assign it to the current smartPtr. the same actions happen with the code modified as you suggested.
SorinA.
I don't know why it leaks (if it does). As others have pointed out writing ref counted pointers is hard, and I could not be bothered to read all your code. However, your code does NOT check correctly for self-assignment, which is what the title suggested your question was about.
anon
I have just copied your code into a text editor and searched for "rVar". Unless I am missing something, this is not declared anywhere.
anon
Steve Jessop
@steve I asumed (before searching the code) that his code was comparing reference counts, when it actually needed to compare identity. But given that his code appears to be completely bogus, I guess all bets are off. This reminds me of why I stopped posting here (too many idiots). I think I'll probably stop again :-(
anon
It would indeed be nice if people compiled their code before posting. If you're planning to spend the equivalent time on Punchtape, though, then goodbye and good riddance :-)
Steve Jessop
@Newil Butterworth rVar = other; i copy pasted from the cpp to h to make the code shorter for you guys to read. Guys like you making people idiots for nothing are the problem, really ;)@Steve Jessop: If i hadn't compiled the code how was i supposed to say what was happening in the log?
SorinA.
@Steve See what I mean? Thanks (I think!) for your Punchtape appreciation - I just posted a new letter, BTW.
anon
@Neil: just been reading it. I particularly like the fact that myA is non-const: bodes well for when the class is fleshed out a bit more. @Sorin: you've compiled *some* code, just not *this* code. Seriously, I think "idiot" is a bit strong, but usually the time it would take questioners to post a complete, compilable code sample is less than the time other people will be delayed by errors that have nothing to do with the real problem.
Steve Jessop
@Neil: If you think the question is badly worded or contains erroneous example code, IMO you should down-vote it and post a comment politely explaining why. I do not think anyone should be called an idiot for making mistakes in posting. (It would be different if that person would do this several times in a row despite polite comments, but nobody gave the impression this is the case here.)
sbi
+2  A: 

You might want to look at the following quote from A Proposal to Add General Purpose Smart Pointers to the Library Technical Report:

The Boost developers found a shared-ownership smart pointer exceedingly difficult to implement correctly. Others have made the same observation. For example, Scott Meyers [Meyers01] says:

"The STL itself contains no reference-counting smart pointer, and writing a good one - one that works correctly all the time - is tricky enough that you don't want to do it unless you have to. I published the code for a reference-counting smart pointer in More Effective C++ in 1996, and despite basing it on established smart pointer implementations and submitting it to extensive pre- publication reviewing by experienced developers, a small parade of valid bug reports has trickled in for years. The number of subtle ways in which reference-counting smart pointers can fail is remarkable."

If this is homework, read about how to implement copy ctor and assignment operator using a swap() (member) function. Otherwise, do not try to write your own smart pointer. You cannot win.

sbi
A: 

My impression is that there is no memory leak. To be sure:

  • test with valgrind or the VS-alternative
  • use std::tr1::shared_ptr (if this is more than educational)
stefaanv
+1  A: 

Your code doesn't compile, which leads me to believe that the version you posted can't be the version you colleagues are complaining about.

Joe Gauterin
+1  A: 

I don't see a leak either, but I think there are some other problems (other than many compiler errors - this cannot be the code you are using):

SmartPtr& operator=(SmartPtr& other)

should take the argument by const reference. You don't have to increment the reference count of other, because you can do it on the non-const left-hand side, as they will be sharing the same reference count instance.

Next, the canonical way to implement assignment for such classes is using the copy-and-swap idiom - which means you should also define a trivial swap method (which just swaps the pointers), and worry less about self-assignment :)

UncleBens
The trouble with copy-and-swap for smart pointers, I suspect, is that it pretty much doubles the cost of assignment by doing an extra increment and decrement for the temporary copy. The overhead of ref-counting is all about that cost, especially if it's supposed to be thread safe (which this implementation isn't, so that's a whole other can of worms).
Steve Jessop
There is no extra decrement and increment. You get one of each: the new reference count will be incremented once by the copy constructor, and the old reference count will be decremented once by the destructor of the temporary. Both might be avoidable only in case of self-assignment, but how often does that happen? (There might be some little overhead as there should be more pointer assignments, but on the other hand it allows you to omit self-assignment tests - go figure. Also, not sure what happens if the compiler inlines and does its tricks, since these classes are normally templated.)
UncleBens
A test (with OP's fixed-up SmartPtr) shows, that you seem to be right though. I called std::rotate on those a whole bunch of times, and got that raw pointers took ~0.8 sec, OPs assignment took ~1.3 sec and copy-swap assignment took ~1.8 sec.
UncleBens
Yeah, you're right, there shouldn't be an extra ref/deref at all. Not sure why I thought there would. I guess it's (ref, pointer copy, pointer swap, deref) vs. (ref, deref, pointer clear, pointer copy) vs (pointer copy). And the clear/copy could be combined if it's all inlined. I agree that optimising self assignment probably isn't worth it, especially since the test might cost something.
Steve Jessop
A: 

Pretty much any smart pointer will have cases where it leaks. It's just the way that it has to be if you implement it using references. There's also a million other problems plus they are slow. Since they are buggier than raw pointers there's really not much use if all you get out of it is reference counting. I have been tempted to use them for some very special purposes but they are not for general programming use. There's a reason that they are not allowed in STL containers for example.

Charles Eli Cheese
Smart pointers are definitely not buggier than raw pointers. My own experience and several experts show that using good smart pointers like boost::shared_ptr simplifies memory management by an order of magnitude.Unfortunately, std::auto_ptr, which is not allowed in STL containers, is not a good example but the standard committee acknowledge the error and it will be deprecated in C++0x.
alexk7