sharedptr.h   [plain text]


/*
*******************************************************************************
* Copyright (C) 2014, International Business Machines Corporation and         
* others. All Rights Reserved.                                                
*******************************************************************************
*                                                                             
* File SHAREDPTR.H                                                             
*******************************************************************************
*/

#ifndef __SHARED_PTR_H__
#define __SHARED_PTR_H__

#include "unicode/uobject.h"
#include "umutex.h"
#include "uassert.h"

U_NAMESPACE_BEGIN

// Wrap u_atomic_int32_t in a UMemory so that we allocate them in the same
// way we allocate all other ICU objects.
struct AtomicInt : public UMemory {
    u_atomic_int32_t value;
};

/**
 * SharedPtr are shared pointers that support copy-on-write sematics.
 * SharedPtr makes the act of copying large objects cheap by deferring the
 * cost of the copy to the first write operation after the copy.
 *
 * A SharedPtr<T> instance can refer to no object or an object of type T.
 * T must have a clone() method that copies
 * the object and returns a pointer to the copy. Copy and assignment of
 * SharedPtr instances are cheap because they only involve copying or
 * assigning the SharedPtr instance, not the T object which could be large.
 * Although many SharedPtr<T> instances may refer to the same T object,
 * clients can still assume that each SharedPtr<T> instance has its own
 * private instance of T because each SharedPtr<T> instance offers only a
 * const view of its T object through normal pointer operations. If a caller
 * must change a T object through its SharedPtr<T>, it can do so by calling
 * readWrite() on the SharedPtr instance. readWrite() ensures that the
 * SharedPtr<T> really does have its own private T object by cloning it if
 * it is shared by using its clone() method. SharedPtr<T> instances handle
 * management by reference counting their T objects. T objects that are
 * referenced by no SharedPtr<T> instances get deleted automatically.
 */

// TODO (Travis Keep): Leave interface the same, but find a more efficient
// implementation that is easier to understand.
template<typename T>
class SharedPtr {
public:
    /**
     * Constructor. If there is a memory allocation error creating
     * reference counter then this object will contain NULL, and adopted
     * pointer will be freed. Note that when passing NULL or no argument to
     * constructor, no memory allocation error can happen as NULL pointers
     * are never reference counted.
     */
    explicit SharedPtr(T *adopted=NULL) : ptr(adopted), refPtr(NULL) {
        if (ptr != NULL) {
            refPtr = new AtomicInt();
            if (refPtr == NULL) {
                delete ptr;
                ptr = NULL;
            } else {
                refPtr->value = 1;
            }
        }
    }

    /**
     * Copy constructor.
     */
    SharedPtr(const SharedPtr<T> &other) :
            ptr(other.ptr), refPtr(other.refPtr) {
        if (refPtr != NULL) {
            umtx_atomic_inc(&refPtr->value);
        }
    }

    /**
     * assignment operator.
     */
    SharedPtr<T> &operator=(const SharedPtr<T> &other) {
        if (ptr != other.ptr) {
            SharedPtr<T> newValue(other);
            swap(newValue);
        }
        return *this;
    }

    /**
     * Destructor.
     */
    ~SharedPtr() {
        if (refPtr != NULL) {
            if (umtx_atomic_dec(&refPtr->value) == 0) {
                delete ptr;
                delete refPtr;
            }
        }
    }

    /**
     * reset adopts a new pointer. On success, returns TRUE.
     * On memory allocation error creating reference counter for adopted
     * pointer, returns FALSE while leaving this instance unchanged.
     */
    bool reset(T *adopted) {
        SharedPtr<T> newValue(adopted);
        if (adopted != NULL && newValue.ptr == NULL) {
            // We couldn't allocate ref counter.
            return FALSE;
        }
        swap(newValue);
        return TRUE;
    }

    /**
     * reset makes this instance refer to no object.
     */
    void reset() {
        reset(NULL);
    }

    /**
     * count returns how many SharedPtr instances, including this one,
     * refer to the T object. Used for testing. Clients need not use in
     * practice.
     */
    int32_t count() const {
        if (refPtr == NULL) {
            return 0;
        }
        return umtx_loadAcquire(refPtr->value);
    }

    /**
     * Swaps this instance with other.
     */
    void swap(SharedPtr<T> &other) {
        T *tempPtr = other.ptr;
        AtomicInt *tempRefPtr = other.refPtr;
        other.ptr = ptr;
        other.refPtr = refPtr;
        ptr = tempPtr;
        refPtr = tempRefPtr;
    }

    const T *operator->() const {
        return ptr;
    }

    const T &operator*() const {
        return *ptr;
    }

    bool operator==(const T *other) const {
        return ptr == other;
    }

    bool operator!=(const T *other) const {
        return ptr != other;
    }

    /**
     * readOnly gives const access to this instance's T object. If this
     * instance refers to no object, returns NULL.
     */
    const T *readOnly() const {
        return ptr;
    }

    /**
     * readWrite returns a writable pointer to its T object copying it first
     * using its clone() method if it is shared.
     * On memory allocation error or if this instance refers to no object,
     * this method returns NULL leaving this instance unchanged.
     * <p>
     * If readWrite() returns a non NULL pointer, it guarantees that this
     * object holds the only reference to its T object enabling the caller to
     * perform mutations using the returned pointer without affecting other
     * SharedPtr objects. However, the non-constness of readWrite continues as
     * long as the returned pointer is in scope. Therefore it is an API
     * violation to call readWrite() on A; perform B = A; and then proceed to
     * mutate A via its writeable pointer as that would be the same as setting
     * B = A while A is changing. The returned pointer is guaranteed to be
     * valid only while this object is in scope because this object maintains
     * ownership of its T object. Therefore, callers must never attempt to
     * delete the returned writeable pointer. The best practice with readWrite
     * is this: callers should use the returned pointer from readWrite() only
     * within the same scope as that call to readWrite, and that scope should
     * be made as small as possible avoiding overlap with other operatios on
     * this object.
     */
    T *readWrite() {
        int32_t refCount = count();
        if (refCount <= 1) {
            return ptr;
        }
        T *result = (T *) ptr->clone();
        if (result == NULL) {
            // Memory allocation error
            return NULL;
        }
        if (!reset(result)) {
            return NULL;
        }
        return ptr;
    }
private:
    T *ptr;
    AtomicInt *refPtr;
    // No heap allocation. Use only stack.
    static void * U_EXPORT2 operator new(size_t size);
    static void * U_EXPORT2 operator new[](size_t size);
#if U_HAVE_PLACEMENT_NEW
    static void * U_EXPORT2 operator new(size_t, void *ptr);
#endif
};

U_NAMESPACE_END

#endif