2

I read:

How to avoid memory leak with shared_ptr?

I know that I need to use weak_ptr to avoid circular reference .

So I created a little program to play circular reference.

Following object(spyder) will be invoke

class spyder {
 public:
  spyder(std::string _name): 
    m_name(_name), finger(nullptr)
  {  }
  inline const std::string ask_name() const{
    return m_name;
  }
  std::shared_ptr<spyder> finger;
 private:
  std::string m_name;
};

I invoke spyder in my main code with shared_ptr and weak_ptr:

int main(){
  auto sA = std::make_shared<spyder>("A");
  auto sB = std::make_shared<spyder>("B");
  std::weak_ptr<spyder> wp_sA(sA);
  std::weak_ptr<spyder> wp_sB(sB);  
  sA->finger = wp_sB.lock();
  sB->finger = wp_sA.lock();
}

Above code has memory leak happened(use valgrind to check).

==20753== Memcheck, a memory error detector
==20753== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==20753== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==20753== Command: ./t
==20753== 
==20753== 
==20753== HEAP SUMMARY:
==20753==     in use at exit: 128 bytes in 2 blocks
==20753==   total heap usage: 3 allocs, 1 frees, 72,832 bytes allocated
==20753== 
==20753== LEAK SUMMARY:
==20753==    definitely lost: 64 bytes in 1 blocks
==20753==    indirectly lost: 64 bytes in 1 blocks
==20753==      possibly lost: 0 bytes in 0 blocks
==20753==    still reachable: 0 bytes in 0 blocks
==20753==         suppressed: 0 bytes in 0 blocks
==20753== Rerun with --leak-check=full to see details of leaked memory
==20753== 
==20753== For counts of detected and suppressed errors, rerun with: -v
==20753== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

But after I modify above code as:

int main(){
  spyder sA("A"), sB("B");
  std::weak_ptr<spyder> wp_sA( std::make_shared<spyder>("A") ) ;
  std::weak_ptr<spyder> wp_sB( std::make_shared<spyder>("B") );
  sA.finger = wp_sB.lock();
  sB.finger = wp_sA.lock();
}

The memory leak hasn't happened,

==20695== Memcheck, a memory error detector
==20695== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==20695== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==20695== Command: ./t
==20695== 
==20695== 
==20695== HEAP SUMMARY:
==20695==     in use at exit: 0 bytes in 0 blocks
==20695==   total heap usage: 3 allocs, 3 frees, 72,832 bytes allocated
==20695== 
==20695== All heap blocks were freed -- no leaks are possible
==20695== 
==20695== For counts of detected and suppressed errors, rerun with: -v
==20695== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

I feel confused about this.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
curlywei
  • 682
  • 10
  • 18
  • 1
    What happens *after* the `lock` calls? Do you ever unlock the weak pointers? Please try to create a [mcve] to show us, and also copy-paste the full output of Valgrind from that example. – Some programmer dude Sep 09 '19 at 10:13
  • 1
    Also I think you're misunderstanding something, in the first example what you're really doing is equal to `sA->finger = sB; sB->finger = sA;`. You need to make the `finger` members `std::weak_ptr`. – Some programmer dude Sep 09 '19 at 10:18
  • Hi @Someprogrammerdude : I've updated utput of Valgrind. command is ```valgrind --leak-check=summary ./t.out ``` – curlywei Sep 09 '19 at 10:19
  • **You cannot fix a circular reference design issue with weak references.** – curiousguy Sep 13 '19 at 17:14

2 Answers2

4

You haven't solved the circular dependency at all.

In your first version,

sA is managed by a shared_ptr and it stores a shared_ptr to sB. sB in turn is managed by shared_ptr and stores a shared_ptr to sA. This means their reference count can never go to 0.

Instead, finger should be of type std::weak_ptr and you should only lock() it before you need to use it.

class spyder {
 public:
  spyder(std::string _name): 
    m_name(_name), finger()
  {  }
  inline const std::string ask_name() const{
    return m_name;
  }
  std::weak_ptr<spyder> finger;
 private:
  std::string m_name;
};

int main(){
  auto sA = std::make_shared<spyder>("A");
  auto sB = std::make_shared<spyder>("B");
  sA->finger = sB;
  sB->finger = sA;
}

Your second version

constructs weak_ptr from a temporary shared_ptr, which means immidiately after construction your weak_ptr is expired(). Both sA.finger and sB.finger store nullptr in this version (but there's no memory leak).

Community
  • 1
  • 1
Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
  • Hi @Yksisarvinen: Thanks your reply. After I modify **std::weak_ptr finger;** this code cannot compile, see std::weak_ptr finger; see **cpp.sh/7a2fm** – curlywei Sep 09 '19 at 10:31
  • @curlywei My bad, didn't run the code before posting. Default constructor for `std::weak_ptr` (and `std::shared_ptr` as well) will initialize it with `nullptr`. You can even omit it from initializer list. Editted the answer now – Yksisarvinen Sep 09 '19 at 10:34
  • After I removed **nullptr**, this code work normally. Thank you very much! – curlywei Sep 09 '19 at 10:36
3

Your code leaks because it still has a circular reference. sA -> sA::finger -> sB -> sB::finger -> sA The main point of std::weak_ptr is to store reference to the pointer. To avoid memory leakage your class source should look like this:

class spyder {
 public:
  spyder(std::string _name): 
    m_name(_name), finger(nullptr)
  {  }
  inline const std::string ask_name() const{
    return m_name;
  }
  std::weak_ptr<spyder> finger;
 private:
  std::string m_name;
};
Ari0nhh
  • 5,720
  • 3
  • 28
  • 33
  • Hi @Ari0nhh: As Yksisarvinen said, **finger(nullptr)** should be **finger()** – curlywei Sep 09 '19 at 10:38
  • 2
    @curlywei For the record, format _inline_ code in comments (or in questions/answers) using single backticks like so: \`myCode\` becomes `myCode`. It's better than using **bold**. – Max Langhof Sep 09 '19 at 11:37