1

I'm implementing my own std::shared_ptr in C++. I have problems, when I do something like this:

#include "shared_ptr.h"

using namespace std;
class T{
public:
    ~T(){
        cout << "~T()" << endl;
    }
};
int main() {
    SharedPTR<T> a(new T[10]);
    return 0;
}

And compiler tells me this:

~T()
my_shared_ptr(42188,0x10dd13dc0) malloc: *** error for object 0x7f90b3c057b8: pointer being freed was not allocated
my_shared_ptr(42188,0x10dd13dc0) malloc: *** set a breakpoint in malloc_error_break to debug

I tried to use std::conditional to choose special deleter for the dependent type Type. And it ain't work. There is shared_ptr.h:

#include <functional>

template<typename Type, class TDeleter = std::default_delete<Type>>
class SharedPTR final{
    using t_SharedPTR = SharedPTR<Type, TDeleter>;
    using ptr_type = Type*;
    using deleter_type = TDeleter;
    deleter_type deleter = typename std::conditional<std::is_array_v<t_SharedPTR>,
            std::default_delete<Type[]>, std::default_delete<Type>>::type();

    ptr_type data = nullptr;
    long* count = nullptr;

    void increment_count(){
        if(data != nullptr){
            (*count)++;
        }
    };
public:
    // https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr
    SharedPTR(){};
    SharedPTR(std::nullptr_t) : data(nullptr), count(nullptr){};
    explicit SharedPTR(ptr_type ptr){
        data = ptr;
        count = new long(0);
        increment_count();
    };
    SharedPTR(const t_SharedPTR& other) : data(other.data), count(other.count){
        increment_count();
    };
    SharedPTR(t_SharedPTR&& other) noexcept : data(other.data), count(other.count), deleter(other.deleter){
        other.data = nullptr;
        other.count = nullptr;
    };
    ~SharedPTR(){
        release();
    };
public:
    t_SharedPTR& operator=(t_SharedPTR&& other) noexcept{
        release();
        data = other.data;
        count = other.count;
        deleter = other.deleter;
        other.data = nullptr;
        other.count = nullptr;
        return *this;
    };
    t_SharedPTR& operator=(Type* ptr){
        release();
        data = ptr;
        if(data != nullptr){
            count = new long(0);
            increment_count();
        }
        return *this;
    };
    t_SharedPTR& operator=(const t_SharedPTR& other){
        release();
        data = other.data;
        count = other.count;
        deleter = other.deleter;
        increment_count();
        return *this;
    };
public:
    Type* get() const {
        return data;
    };
    operator bool() const{
        return get() != nullptr;
    };
    // Dereferences the stored pointer. The behavior is undefined if the stored pointer is null. (cpp_reference)
    Type& operator*() const {
        return *get();
    };
    Type* operator->() const {
        return get();
    };

    TDeleter& get_deleter(){
        return deleter;
    };

    long use_count() const {
        return count ? *count : 0;
    }

    bool unique() const {
        return use_count() == 1;
    }

public:
    void release(){
        if(count != nullptr){
            if(*count == 1){
                deleter(data);
                delete count;
            }
            else {
                (*count)--;
            }
        };
    };

    void reset(ptr_type ptr = nullptr){
        release();
        data = ptr;
        count = new long(0);
        increment_count();
    };
    void swap(t_SharedPTR& sharedPTR){
        std::swap(data, sharedPTR.data);
        std::swap(count, sharedPTR.count);
    };
};
  • 5
    The way to replicate the behavior of `std::shared_ptr` is to use `SharedPTR` vs `SharedPTR` as appropriate, where you'd use the latter in this case, so `delete[]` is called instead of `delete` since it needs to match your `new[]` call. – Cory Kramer Oct 22 '21 at 13:34
  • @CoryKramer, i don't want to replicate the behavior of ```std::shared_ptr``` at all. I want to implement my code, where ```SharedPTR a(new T[10]);``` works and after working of my smart pointer it will call destructor 10 times. – Dilshod Nozimov Oct 22 '21 at 13:45
  • 2
    You do *not* want to call the destructor 10 times, you need exactly 1 `delete[]` call to match your `new T[10]` call, that is *not* the same thing as calling `delete` on each of the 10 elements of the array. https://stackoverflow.com/questions/2425728/delete-vs-delete-operators-in-c Or are you saying you're going to internally copy your `new T[10]` to 10 `new T` calls? – Cory Kramer Oct 22 '21 at 13:47
  • @CoryKramer, no, i don't wanna copy. I understand that, when i call ```new[]```, i need to call 1 exactly ```delete[]```. But at same time, i want to implement class when code like ```SharedPTR p1(new T);``` and ```SharedPTR p2(new T[10]);``` works at the same time. In first case my class should call ```delete``` (and it works), in second case it should call ```delete[]``` (and it doesn't work). – Dilshod Nozimov Oct 22 '21 at 13:56
  • 1
    There is no way of distinguishing a what you get from `new T` from what get from `new T[N]`, they are both expressions of type `T*`. – Caleth Oct 22 '21 at 14:03
  • Perhaps you want `make_shared` and `make_shared_array`, and don't (publicly) allow construction from `T*` (on it's own) – Caleth Oct 22 '21 at 14:05
  • 3
    There is no way at construction to distinguish between a pointer to a single element (which you would `delete`) from a pointer to the start of an array (which you would `delete[]`). You need to encode this information some other way. The usual method is in the smart pointer's template argument. for example `SharedPTR` could mean "pointer to a single `T`" where as `SharedPTR` could mean "pointer to an array of `T`". This is how `std::shared_ptr` and `std::unique_ptr` do it. Or you could try passing an extra construction argument (though this makes assignment harder). – François Andrieux Oct 22 '21 at 14:08
  • @FrançoisAndrieux, yep, i did that with partial specialization of my class: ```class```, but the main problem for this solution is code duplication. And I don't like it. – Dilshod Nozimov Oct 22 '21 at 14:14
  • 1
    You already need specialization as `operator[]` make sense only for arrays, for example. – Phil1970 Oct 22 '21 at 15:27

0 Answers0