-1

All the code is included in this post. For some reason, it tells me that, a memory leak is happening at shared_ptr.hpp(60), assuming this must be line 60 here. But, why exactly? This happens is uncertain.

Anyone that can spot the issue, please let me know for certain why, and what the best fix is here. I been spending all day yesterday, with no results of whatsoever.

It is here, according to the leaked output:

explicit SharedPtr(T* other) : _ptr{ other }, _ctrl_block{new ControlBlock<T>}
        {
            __increment_reference();
            CHECK 
        }

LEAK:

Detected memory leaks!
Dumping objects ->
C:\Users\root\cpp\ptrs\ptrs\shared_ptr.hpp(60) : {731} normal block at 0x000001B15AE83130, 8 bytes long.
 Data: <        > 00 00 00 00 01 00 00 00 
C:\Users\root\cpp\ptrs\ptrs\shared_ptr.hpp(60) : {727} normal block at 0x000001B15AE830E0, 8 bytes long.
 Data: <        > 00 00 00 00 01 00 00 00 
C:\Users\root\cpp\ptrs\ptrs\shared_ptr.hpp(60) : {721} normal block at 0x000001B15AE83090, 8 bytes long.
 Data: <        > 00 00 00 00 01 00 00 00 
C:\Users\root\cpp\ptrs\ptrs\shared_ptr.hpp(60) : {717} normal block at 0x000001B15AE82E60, 8 bytes long.
 Data: <        > 00 00 00 00 01 00 00 00 
C:\Users\root\cpp\ptrs\ptrs\shared_ptr.hpp(60) : {688} normal block at 0x000001B15AE83040, 8 bytes long.
 Data: <        > 00 00 00 00 01 00 00 00 
C:\Users\root\cpp\ptrs\ptrs\shared_ptr.hpp(60) : {686} normal block at 0x000001B15AE82FF0, 8 bytes long.
 Data: <        > 00 00 00 00 01 00 00 00 
C:\Users\root\cpp\ptrs\ptrs\shared_ptr.hpp(60) : {684} normal block at 0x000001B15AE82F50, 8 bytes long.
 Data: <        > 00 00 00 00 01 00 00 00 
Object dump complete.
The program '[34840] ptrs.exe' has exited with code 0 (0x0).

controlblock.hpp

template<class T>
struct ControlBlock
{
    int _ref_count;
    int _weak_count;
    void inc_ref() noexcept { ++_ref_count; }
    void inc_wref() noexcept { ++_weak_count; }
    void dec_ref() noexcept { --_ref_count; }
    void dec_wref() noexcept { if (--_weak_count == 0) delete this; }
    int use_count() const noexcept { return _ref_count; }
    ControlBlock() : _ref_count{ 0 }, _weak_count{ 0 } {}
    ~ControlBlock() { _ref_count = 0; _weak_count = 0; }
};

shared_ptr.hpp

#pragma once

#include <algorithm>
#include <iterator>
#include <compare>
#include <cassert>
#include <memory>

#include "controlblock.hpp"
#include "weak_ptr.hpp"

template<class T>
class SharedPtr {
#define CHECK assert(Invariant());
    template<class Y> friend class WeakPtr;
private:
    T* _ptr;
    ControlBlock<T>* _ctrl_block;

    void __increment_reference()
    {
        if (_ptr != nullptr && _ctrl_block != nullptr)
                _ctrl_block->inc_ref();
    }

    void __remove_reference()
    {
        if (_ctrl_block && _ctrl_block->_ref_count > 0) {
            --_ctrl_block->_ref_count;
            if(_ctrl_block && _ctrl_block->_ref_count == 0){
                delete _ptr;
            }
            
            if(_ctrl_block->_ref_count + _ctrl_block->_weak_count == 0) {
                delete _ctrl_block;
            }
            
            _ptr = nullptr;
            _ctrl_block = nullptr;
        }
    }

public:
    constexpr SharedPtr() : _ptr{ nullptr }, _ctrl_block{ nullptr } { }
    explicit SharedPtr(T* other) : _ptr{ other }, _ctrl_block{new ControlBlock<T>}
    {
        __increment_reference();
        CHECK 
    }
    constexpr SharedPtr(const std::nullptr_t) noexcept 
        : _ptr{ nullptr }, _ctrl_block{ nullptr } { CHECK }
    
    explicit SharedPtr(WeakPtr<T>& other)
        : _ptr{other._ptr}, _ctrl_block{other._ctrl_block}
    {
        if (other.expired())
            throw std::bad_weak_ptr();
        
        _ctrl_block->inc_wref();
        CHECK
    }

    SharedPtr(const SharedPtr& other) noexcept 
        : _ptr{other._ptr}, _ctrl_block{other._ctrl_block}
    {
        __increment_reference();
        CHECK
    }

    SharedPtr(SharedPtr&& other) noexcept 
        : _ptr{ std::exchange(other._ptr, nullptr) }, 
          _ctrl_block{std::exchange(other._ctrl_block, nullptr)} 
    { 
        CHECK 
    }
    
    ~SharedPtr() { __remove_reference(); CHECK };

    SharedPtr& operator=(const SharedPtr& other) noexcept 
    {
        if (this == &other)
            return *this;

        __remove_reference();
        _ptr = other._ptr;
        _ctrl_block = other._ctrl_block;
        __increment_reference();

        CHECK
        return *this;
    }

    SharedPtr& operator=(SharedPtr&& other) noexcept
    {
        if (this == &other)
            return *this;

        __remove_reference();
        _ptr = std::exchange(other._ptr, nullptr);
        _ctrl_block = std::exchange(other._ctrl_block, nullptr);

        CHECK 
        return *this;
    }

    SharedPtr& operator=(std::nullptr_t)
    {
        if (_ptr == nullptr && _ctrl_block == nullptr)
            return *this;

        __remove_reference();
        return *this;
    }

    T* get() const noexcept { return _ptr; }
    T& operator*() const noexcept { return *_ptr; }
    T* operator->() const noexcept { return _ptr; }
    operator bool() const noexcept { return _ptr != nullptr && _ctrl_block != nullptr; }

    size_t use_count() const noexcept { return (_ctrl_block) ? _ctrl_block->use_count() : 0; }
    
    void reset() noexcept { this->__remove_reference(); }
    bool Invariant() const noexcept;

    template<class T> friend void swap(SharedPtr<T>& lhs, SharedPtr<T>& rhs) noexcept;

    friend auto operator<=>(const SharedPtr& lhs, const SharedPtr& rhs) = default;
    friend auto operator==(const SharedPtr& lhs, const SharedPtr& rhs) 
    {
        if (lhs.get() != rhs.get())
            return false;

        return (lhs.get() <=> rhs.get()) == 0;
    }
};

template<class T>
inline bool SharedPtr<T>::Invariant() const noexcept
{
    if (_ptr == nullptr && _ctrl_block == nullptr)
        return true;
    else if (_ptr != nullptr || _ctrl_block != nullptr && 
           _ctrl_block->_ref_count > 0 || _ctrl_block->_weak_count > 0)
        return true;
    
    return false;
}

template<class T> void swap(SharedPtr<T>& lhs, SharedPtr<T>& rhs) noexcept 
{
    std::swap(lhs, rhs);
}

template<class T, class ...Args>
SharedPtr<T> MakeShared(Args && ...args)
{
    return SharedPtr<T>(new T(std::forward<Args>(args)...));
}

weak_ptr.hpp

#pragma once

#include "controlblock.hpp"
#include "shared_ptr.hpp"

template<class T>
class WeakPtr {
#define CHECK assert(Invariant());
    template<class Y> friend class SharedPtr;
private:
    T* _ptr;
    ControlBlock<T>* _ctrl_block;
public:
    WeakPtr() { CHECK }
    WeakPtr(T* other) : _ptr{other}, _ctrl_block{new ControlBlock<T>()} { __increment_weakptr(); CHECK }
    
    WeakPtr(const WeakPtr& other) noexcept : _ptr{other._ptr}, _ctrl_block{other._ctrl_block}
    { 
        __increment_weakptr(); 
        CHECK 
    }

    WeakPtr(const SharedPtr<T>& other) noexcept 
        : _ptr{other._ptr}, _ctrl_block{other._ctrl_block}
    { 
        if(_ptr != nullptr && _ctrl_block != nullptr)
            _ctrl_block->inc_wref();
        CHECK 
    }

    ~WeakPtr() { __decrement_weakptr(); CHECK }

    void reset() noexcept
    {
        this->__decrement_weakptr();
    }
    
    bool expired() noexcept
    {
        if (_ctrl_block == nullptr)
            return true;

        if (_ctrl_block->_ref_count == 0)
            --_ctrl_block->_weak_count;

        if (_ctrl_block->_ref_count + _ctrl_block->_weak_count == 0) {
            delete _ctrl_block;
            _ptr = nullptr;
            _ctrl_block = nullptr;
        }

        return !_ctrl_block || _ctrl_block->_weak_count == 0;
    }

    auto lock()
    {
        return expired() ? SharedPtr<T>() : SharedPtr<T>(*this);
    }

    bool Invariant() const noexcept 
    {
        if (_ptr == nullptr)
            return true;
        return _ctrl_block->_weak_count > 0;
    }
    
    template<class T> 
    friend void swap(WeakPtr<T>& lhs, WeakPtr <T>& rhs) noexcept;

    WeakPtr& operator=(const WeakPtr& other) 
    {
        if (this != &other) {
            __decrement_weakptr();
            _ptr = other._ptr;
            _ctrl_block = other._ctrl_block;
            __increment_weakptr();
        }

        CHECK
        return *this; 
    }

    WeakPtr& operator=(const SharedPtr<T>& other) {
        _ptr = other._ptr;
        _ctrl_block = other._ctrl_block;

        if(_ptr != nullptr && _ctrl_block != nullptr)
            _ctrl_block->inc_wref();

        CHECK
        return *this;
    }

private:
    void __increment_weakptr()
    {
        if (_ctrl_block != nullptr) {
            _ctrl_block->inc_wref();
        }
    }

    void __decrement_weakptr()
    {
        if (_ctrl_block != nullptr && _ptr != nullptr) {
            if (--_ctrl_block->_weak_count == 0 && _ctrl_block->_ref_count == 0) {
                delete _ctrl_block;
                delete _ptr;
            }
        }
        _ptr = nullptr;
        _ctrl_block = nullptr;
    }
};

template<class T>
inline void swap(WeakPtr<T>& lhs, WeakPtr<T>& rhs) noexcept
{
    std::swap(lhs, rhs);
}

Reproduction with one Leak:

#include <iostream>
using std::cout;

#include <memory>
#include <cassert>

#include "shared_ptr.hpp"

void Errorx(const char* msg) {
    cout << msg << std::endl;
    __debugbreak();
}

struct Char {
    int _state = 1;
    char _c;
    ~Char() {
        if (_state != 1)
            if (_state == -1)
                Errorx("Probably trying to delete an item for the second time!");
            else if (_state == 0)
                Errorx("You are probably trying to delete an object that you have not designed!");
            else
                Errorx("Maybe you are trying to delete an object that you have not designed or something else is wrong");
        _state = -1;
    }
    Char(char c = 'x') {
        _c = c;
    }
    Char& operator=(const Char&) = delete;
};

#define TestShared SharedPtr<Char>
#define TestWeak WeakPtr<Char>

int main() {
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

    TestShared shrd(new Char('b'));
    TestWeak weak(shrd);
    assert(!weak.expired());
    assert(weak.Invariant());
    TestShared shrd2 = weak.lock();
    assert(shrd == shrd2);
    TestWeak weak0;
    TestShared shrd3 = weak0.lock();
    assert(weak0.expired());
    
    std::cin.get();
    return 0;
}
Albin M
  • 109
  • 6
  • 4
    Is it your custom `shared_ptr`, or did you find it somewhere? Can you add a small snippet with a `main` function that we can run to reproduce? – HolyBlackCat Apr 27 '22 at 06:36
  • @HolyBlackCat, it is my own custom written shared_ptr. – Albin M Apr 27 '22 at 06:37
  • 7
    I hope you do this as a learning exercise only, because otherwise you should just use the standards variants. Also note that symbols starting with double underscores are reserved for the "the implementation" (compiler and the standard library implementation), you should not define such symbols in your own code (see [What are the rules about using an underscore in a C++ identifier?](https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier)) – Some programmer dude Apr 27 '22 at 06:43
  • 2
    You should consider to use in multithreads context as well – nhatnq Apr 27 '22 at 07:30
  • 1
    Hm, it's curious how they are depending on each other. My money is on lock() and construction of weak pointer. Standard library does that differently – Swift - Friday Pie Apr 27 '22 at 08:54
  • 3
    I can see a double-delete problem. When `_ref_count==0` the last `weak_ptr` and the last `shared_ptr` do `delete ptr`, which is wrong. `weak_ptr` does not modify `_ref_count`, so it mustn't `delete ptr`. It is normal - the purpose of `weak_ptr` - for `_weak_count` to be positive while `_ref_count==0` - in which case `ptr` has already been `delete`d. – Red.Wave Apr 27 '22 at 20:50
  • 1
    `expired` is problematic too. It literally destructs the object, then indirects a `nullptr` in final `return` statement; although `||` is short circuit, this looks not good. I`d rather keep the final `return` and remove the rest. – Red.Wave Apr 27 '22 at 21:05
  • 1
    Another problem with `weak_ptr` is that when initialized to `nullptr`, it would never `delete _ctrl_block`; a true memory leak. – Red.Wave Apr 27 '22 at 21:16
  • 1
    Still has the bugs I pointed out during code review: https://codereview.stackexchange.com/a/275669/507 – Martin York Apr 27 '22 at 21:18
  • 1
    `weak_ptr()` does not explii zero initialize; you can at least default the ptrs to 0 upon definition. – Red.Wave Apr 27 '22 at 21:24
  • @MartinYork I see good points in that link; why is the same question in 2 places?! – Red.Wave Apr 27 '22 at 21:30
  • @Red.Wave, Not the same question, they are totally different. – Albin M Apr 28 '22 at 06:34
  • @MartinYork, what do you suggest? – Albin M Apr 28 '22 at 06:34

2 Answers2

3
    void __decrement_weakptr()
    {
        if (_ctrl_block != nullptr && _ptr != nullptr) {
            if (--_ctrl_block->_weak_count == 0 && _ctrl_block->_ref_count == 0) {
                delete _ctrl_block;
                delete _ptr;
            }
        }
        _ptr = nullptr;
        _ctrl_block = nullptr;
    }

Weak pointers should not destroy _ptr - the last shared pointer should have done that when _ref_count became zero.

    explicit SharedPtr(WeakPtr<T>& other)
        : _ptr{other._ptr}, _ctrl_block{other._ctrl_block}
    {
        if (other.expired())
            throw std::bad_weak_ptr();
        
        _ctrl_block->inc_wref();
        CHECK
    }

Constructing a SharedPtr should not increment the weak refcount! Why aren't you using the same __increment_reference() as the other constructors?

Note also that ControlBlock::dec_wref is wrong: a zero weak refcount shouldn't immediately delete the control block. What if the strong refcount is still non-zero? You don't seem to use this method anyway, but you should either fix it or remove it.

All of your problems can be diagnosed by printing out the control block state before & after every operation. It'll be verbose but informative. This will be much easier to implement if you start using the inc/dec_(w)ref methods consistently. Just make the data members private, add an accessor for weak_count, and fix the build.

Useless
  • 64,155
  • 6
  • 88
  • 132
0

I've been trying to point to every bug in the code for 20 minutes.
I wasn't able to do that without rewriting ALL of your code… So I did:

// file: 'shared_ptr.hpp'
#include <memory>  // keep this
template <class T> using SharedPtr = std::shared_ptr<T>; // Add this.
// remove anything else.

The rest of the code is either redundant, or (for the most part) ill-formed.

viraltaco_
  • 814
  • 5
  • 14
  • I am NOT using the built-in std::shared_ptr, sorry. I have implemented my own versions, please look closely. I am not using built-in. Sorry. – Albin M May 01 '22 at 12:56
  • Could you share (no pun intended) your reasons for that? Are you coding for an embedded system or something? – Paul Sanders May 01 '22 at 22:42
  • @PaulSanders, why does it matter so much? I am doing this, and I am not sure what in my design choice is causing this. The complete code has been posted including reproduction. What else do you need? – Albin M May 02 '22 at 07:20
  • 1
    It matters because you're probably trying to solve a problem that does not (or need not) exist and I don't like to see people wasting their time. Attempting to write a replacement for an existing STL container that already does what you need is pointless (except perhaps as a learning exercise) and rarely ends well. – Paul Sanders May 02 '22 at 08:40
  • This is part of a, **learning exercise**, not intended to replace the STL. – Albin M May 02 '22 at 10:20
  • Learning by implementing your own. Not intended to replace anything, nor to be used in production. – Albin M May 02 '22 at 10:30