herohuyongtao herohuyongtao - 3 months ago 32
C++ Question

A simple implementation of Smart Pointer Class

In book

C++ Primer 13.5.1
, it implement a Smart Pointer Class using a Use-Count Class. Their implementation is as follows:


  • Use-Count Class

    // private class for use by HasPtr only
    class U_Ptr {
    friend class HasPtr;
    int *ip;
    size_t use;
    U_Ptr(int *p): ip(p), use(1) { }
    ~U_Ptr() { delete ip; }
    };

  • Smart Pointer Class

    /*
    smart pointer class: takes ownership of the dynamically allocated
    object to which it is bound

    User code must dynamically allocate an object to initialize a HasPtr
    and must not delete that object; the HasPtr class will delete it
    */
    class HasPtr {
    public:
    // HasPtr owns the pointer; p must have been dynamically allocated
    HasPtr(int *p, int i)
    : ptr(new U_Ptr(p)), val(i) { }

    // copy members and increment the use count
    HasPtr(const HasPtr &orig)
    : ptr(orig.ptr), val(orig.val) { ++ptr->use; }

    HasPtr& operator=(const HasPtr&);

    // if use count goes to zero, delete the U_Ptr object
    ~HasPtr() { if (--ptr->use == 0) delete ptr; }

    friend ostream& operator<<(ostream&, const HasPtr&);
    // copy control and constructors as before

    // accessors must change to fetch value from U_Ptr object
    int *get_ptr() const { return ptr->ip; }
    int get_int() const { return val; }

    // change the appropriate data member
    void set_ptr(int *p) { ptr->ip = p; }
    void set_int(int i) { val = i; }

    // return or change the value pointed to, so ok for const objects
    // Note: *ptr->ip is equivalent to *(ptr->ip)
    int get_ptr_val() const { return *ptr->ip; }
    void set_ptr_val(int i) { *ptr->ip = i; }
    private:
    U_Ptr *ptr; // points to use-counted U_Ptr class
    int val;
    };






Wonder: I am curious about why not simply using a
int *
to act like the
Use-Count Class
, just like the
int* countPtr;
used in the following new
Smart Pointer Class
:

class T
{
private:
int* countPtr; //

int* p;
int val;

public:
T(){
p = new int();
countPtr = new int();
*countPtr = 1;
val = 0;
}
T(T& t){
p = t.p;
countPtr = t.countPtr;
val = t.val;
*countPtr += 1;
}
T& operator = ( const T& rT){
if(*countPtr>1){
*countPtr -= 1;
}
else{
delete p;
delete countPtr;
}
p = rT.p;
countPtr = rT.countPtr;
val = rT.val;
*countPtr += 1;
return *this;
}
~T(){
if(*countPtr>1){
*countPtr -= 1;
}
else{
delete p;
delete countPtr;
}
}

int *get_ptr() const { return p; }
int get_int() const { return val; }

// change the appropriate data member
void set_ptr(int *ptr) { p = ptr; }
void set_int(int i) { val = i; }
};





Test: I tested the above
Smart Pointer Class
using code like the following and it seems working well.

int main()
{
T t1;
T t2(t1);
T t3(t1);
T t4;
t4 = t1;

return 0;
}





Real question: Is this new
Smart Pointer Class
with simply a
int *countPtr
sufficient enough? If yes, why bother to use an extra
Use-Count Class
like in the book? If no, what do I miss?

Answer

One property of the original implementation is that the delete is performed, in the control block object, with the original pointer type. This is a partial type erasure. No matter how much the smart pointer objects are copied, with somewhat different types, the original control block remains the same, with delete via the original pointer type.

However, since the original code you show is not templated, one must assume that it is an early example, followed later by similar templated code.

Converting a pointer up in a base class hierarchy, as can happen with copying of a smart pointer, means that delete on the new pointer type is only valid if the statically known new type has a virtual destructor.

For example, std::shared_ptr also deletes (guaranteed) via the original pointer type, unless one explicitly supplies a deleter functor that does something else.