1
\$\begingroup\$

I have written a class to encapsulate pointers for a platform, where the STL is not available. The idea is to have something similar to std::shared_ptr. However, I don't want to delete the encapsulated object automatically, once the last reference is gone. Instead, I force the application code to do so by asserting.

template <typename T>
class checked_ptr {
private:
    struct checked_object_wrapper {
        T* raw_instance;
        int reference_count;
    };

    checked_object_wrapper* wrapped_object;

    checked_ptr(checked_object_wrapper* const object) : wrapped_object{object} {
    }

public:
    checked_ptr() : wrapped_object{nullptr} {
    }

    checked_ptr(const checked_ptr& other) : wrapped_object{other.wrapped_object} {
        if(this->wrapped_object) {
            this->wrapped_object->reference_count++;
        }
    }

    checked_ptr(checked_ptr&& other) : wrapped_object{other.wrapped_object} {
        other.wrapped_object = nullptr;
    }

    checked_ptr(T*&& raw) : checked_ptr{new checked_object_wrapper{raw, 1}} {
    }

    ~checked_ptr() {
        reset();
    }

    checked_ptr& operator=(const checked_ptr& other) {
        ASSERT(this->wrapped_object == nullptr);

        if(this->wrapped_object != other.wrapped_object) {
            this->wrapped_object = other.wrapped_object;
            if(this->wrapped_object) {
                this->wrapped_object->reference_count++;
            }
        }
        return *this;
    }

    checked_ptr& operator=(checked_ptr&& other) {
        ASSERT(this->wrapped_object == nullptr);

        this->wrapped_object = other.wrapped_object;
        other.wrapped_object = nullptr;
        return *this;
    }

    bool operator==(const checked_ptr& other) const {
        return this->wrapped_object == other.wrapped_object;
    }

    bool operator!=(const checked_ptr& other) const {
        return this->wrapped_object != other.wrapped_object;
    }

    void reset() {
        if(this->wrapped_object) {
            this->wrapped_object->reference_count--;
            ASSERT(this->wrapped_object->reference_count > 0);

            this->wrapped_object = nullptr;
        }
        return;
    }

    /**
     * This should only be used during deconstruction at the end of the runtime
     */
    void force_reset() {
        if(this->wrapped_object) {
            this->wrapped_object->reference_count--;

            if(this->wrapped_object->reference_count == 0) {
                delete this->wrapped_object->raw_instance;
                delete this->wrapped_object;
            }

            this->wrapped_object = nullptr;
        }
        return;
    }

    T* get() {
        if(this->wrapped_object) {
            return this->wrapped_object->raw_instance;
        } else {
            return nullptr;
        }
    }

    T& operator*() {
        ASSERT(this->wrapped_object != nullptr);
        ASSERT(this->wrapped_object->raw_instance != nullptr);

        return *(this->wrapped_object->raw_instance);
    }

    T* operator->() {
        ASSERT(this->wrapped_object != nullptr);
        ASSERT(this->wrapped_object->raw_instance != nullptr);

        return this->wrapped_object->raw_instance;
    }

    const T* operator->() const {
        ASSERT(this->wrapped_object != nullptr);
        ASSERT(this->wrapped_object->raw_instance != nullptr);

        return this->wrapped_object->raw_instance;
    }

    long int use_count() const noexcept {
        if(this->wrapped_object) {
            return this->wrapped_object->reference_count;
        } else {
            return 0;
        }
    }

    explicit operator bool() const noexcept {
        return this->wrapped_object != nullptr;
    }

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

    void deleteObject() {
        ASSERT(unique());

        if(this->wrapped_object) {
            this->wrapped_object->reference_count--;

            ASSERT(this->wrapped_object->raw_instance != nullptr);
            delete this->wrapped_object->raw_instance;

            delete this->wrapped_object;
            this->wrapped_object = nullptr;
        }
    }
};

This utility function can be used like std::make_shared to create fresh instances to new objects:

template <typename T, typename... A>
checked_ptr<T> make_checked(A&&... arg) {
    return checked_ptr<T>{new T{arg...}};
}

I wrote this class to deal with objects being lost and not deleted by my code and I expected that this class would find all memory leaks.

Any thoughts on this? Especially the additional dynamically allocated memory for my checked_object_wrapper gives me a headache, but I couldn't figure out a better way to store the meta information (the reference count) in a generic fashion. (Note the additional requirement that I can't always use make_checked to create new instances, sometimes I have to use pointers that come from other libraries. Hence, the public constructor that takes a raw pointer is necessary.)

\$\endgroup\$
  • 1
    \$\begingroup\$ are you running in debug mode? otherwise the assert will never fire \$\endgroup\$ – ratchet freak Jan 13 '17 at 12:20
  • \$\begingroup\$ @ratchetfreak Yes, I am running in debug mode. I also have already found bugs through asserts using this wrapper. Unfortunately, none of them were the memory leak that I am looking for. \$\endgroup\$ – Maximilian Köstler Jan 13 '17 at 12:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.