Sheldon Sheldon - 1 year ago 65
C++ Question

C++ Custom Smart Pointer

Recently I tried to implement my own version of a smart pointer. The implementation looks a bit like the following:

class Var {
private:
void* value;
unsigned short* uses;
public:
Var() : value(nullptr), uses(new unsigned short(1)) { }
template<typename K>
Var(K value) : value((void*)new K(value)), uses(new unsigned short(1)) { }
Var(const Var &obj) {
value = obj.value;
(*(uses = obj.uses))++;
}
~Var() {
if (value == nullptr && uses == nullptr) return;
if (((*uses) -= 1) <= 0) {
delete value;
delete uses;
value = uses = nullptr;
}
}
Var& operator=(const Var& obj) {
if (this != &obj) {
this->~Var();
value = obj.value;
(*(uses = obj.uses))++;
}
return *this;
}
};


The implementation should be straight forward, as
value
holds the pointer and
uses
counts the references.

Please note the pointer is stored as a
void*
and the pointer class is not fixed to certain (generic) type.

The Problem



Most of the time the smart pointer does it's job... the exception being the following:

class C {
public:
Var var;
C(Var var) : var(var) {}
};
void test() {
std::string string = std::string("Heyo");
Var var1 = Var(string);
C c = C(var1);
Var var2 = Var(c);
}
void main() {
test();
}


When running that code the very first instance,
var1
, does not get deleted after
test
has run.

Yes, using a
void*
is not exactly the finest of methods. Yet lets not get off topic. The code compiles perfectly fine (if one might question my use of sub-assign operator). And if the error would be in the deletion of a
void*
the reference counter,
uses
, would be deleted but it is not.

I have checked with the destructors before and they all get called as they should.

Do also note that the programm runs without errors.

Thank You all in advance,

Sheldon

Answer Source

Three big problems I see with your code are:

  1. you are storing the allocated object pointer as a void*, and then calling delete on it as-is. That will not call the object's destructor. You must type-cast the void* back to the original type before calling delete, but you can't do since you have lost the type info after the Var constructor exits.

  2. you have separated the object pointer and the reference counter from each other. They should be kept together at all times. Best way to do that is to store them in a struct, and then allocate and pass that around as needed.

  3. your operator= is calling this->~Var(), which is completely wrong. Once you do that, the object pointed to by this is no longer valid! You need to keep the instance alive, so simply decrement its current reference counter, freeing its stored object if needed, and then copy the pointers from the source Var and increment that reference counter.

Try this alternate implementation instead (Live Demo):

class Var
{
private:
    struct controlBlockBase
    {
        unsigned short uses;    

        controlBlockBase() : uses(1) { }
        virtual ~controlBlockBase() { }
    };

    template <class K>
    struct controlBlockImpl : controlBlockBase
    {
        K value;
        controlBlockImpl(const K &val) : controlBlockBase(), value(val) {}
    };

    controlBlockBase *cb;

public:
    Var() : cb(nullptr) { }

    template<typename K>
    Var(const K &value) : cb(new controlBlockImpl<K>(value)) { }

    Var(const Var &obj) : cb(obj.cb) {
        if (cb) {
            ++(cb->uses);
        }
    }

    Var(Var &&obj) : cb(nullptr) {
        obj.swap(*this);
    }

    ~Var() {
        if ((cb) && ((cb->uses -= 1) <= 0)) {
            delete cb;
            cb = nullptr;
        }
    }

    Var& operator=(const Var& obj) {
        if (this != &obj) {
            Var(obj).swap(*this);
        }
        return *this;
    }

    Var& operator=(Var &&obj) {
        obj.swap(*this);
        return *this;
    }

    /* or, the two above operator= codes can be
    merged into a single implementation, where
    the input parameter is passed by non-const
    value and the compiler decides whether to use
    copy or move semantics as needed:

    Var& operator=(Var obj) {
        obj.swap(*this);
        return *this;
    }    
    */

    void swap(Var &other)
    {
        std::swap(cb, other.cb);
    }

    unsigned short getUses() const {
        return (cb) ? cb->uses : 0;
    }

    template<class K>
    K* getAs() {
        if (!cb) return nullptr;
        return &(dynamic_cast<controlBlockImpl<K>&>(*cb).value);
    }
};

void swap(Var &v1, Var v2) {
    v1.swap(v2);
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download