views:

230

answers:

7

I have an auto pointer class and in the constructor I am passing in a pointer. I want to be able to separate new from new[] in the constructor so that I can properly call delete or delete[] in the destructor. Can this be done through template specialization? I don't want to have to pass in a boolean in the constructor.

    template <typename T>
    class MyAutoPtr
    {
    public:
      MyAutoPtr(T* aPtr);
    };

// in use:
MyAutoPtr<int> ptr(new int);
MyAutoPtr<int> ptr2(new int[10]);
+7  A: 

Unfortunately, no. Both return the same type, T*. Consider using builder functions that call an appropriate overloaded constructor:

template <typename T>
class MyAutoPtr
{
public:
    MyAutoPtr(T* aPtr, bool array = false);
};

template <typename T>
MyAutoPtr<T> make_ptr() {
    return MyAutoPtr<T>(new T(), false);
}

template <typename T>
MyAutoPtr<T> make_ptr(size_t size) {
    return MyAutoPtr<T>(new T[size], true);
}

Now you can instantiate objects as follows:

MyAutoPtr<int> ptr = make_ptr<int>();
MyAutoPtr<int> ptr2 = make_ptr<int>(10);
Konrad Rudolph
There is a way to distinguish pointers and arrays when passed into a templetized function, unfortunately, as far as the compiler is concerned, new and new[] match the exact same return type.
Ramon Zarazua
@Ramon: it is possible to distinguish pointers from arrays but `new[]` **does not** create an array. Arrays in C++ are only those types that are created using the `T[N]` syntax, with `N` being a compile-time constant.
Konrad Rudolph
`new[]` does create an array. It just doesn't return a pointer-to-array, it returns a pointer to the first element of the array it has created. 5.3.4/1: "If [the entity] is an array, the *new-expression* returns a pointer to the initial element of the array".
Steve Jessop
@Steve: yes, poor choice of words. What I meant was a C-style array with the static type `T[N]` (which is what matters here).
Konrad Rudolph
Yes, the closest you can get with `new[]` is to get a reference to the "correct" type, meaning you'd have to know N at compile time. I guess in some cases N might be fixed, but too big for the stack.
Steve Jessop
+1  A: 

On the other hand, you could use a specific make function.

template <class T>
MyAutoPtr<T> make();

template <class T>
MyAutoPtr<T> make(size_t n);

Of course, this means that you have the appropriate logic behind, but it's encapsulated. You can also add overload taking a T to copy the object passed into the pointer newly created etc...

Finally, it can also be done with overloads of the constructor... the point is not to call the new outside.

Matthieu M.
+2  A: 

std::unique_ptr in C++0x will have a specialization for dynamic arrays, somewhat like shown below. However, it will be the user's task to instantiate an appropriate instance. At language level there is no way to distinguish one pointer from another.

template <class T>
class pointer
{
    T* p;
public:
    pointer(T* ptr = 0): p(ptr) {}
    ~pointer() { delete p; }
    //... rest of pointer interface
};

template <class T>
class pointer<T[]>
{
    T* p;
public:
    pointer(T* ptr = 0): p(ptr) {}
    ~pointer() { delete [] p; }
    //... rest of pointer and array interface
};

int main()
{
    pointer<int> single(new int);
    pointer<int[]> array(new int[10]);
}

Furthermore, it might not be that good to load one class with so various tasks. For example, boost has shared_ptr and shared_array.

visitor
Actually I don't understand why Boost has both, since the `shared_ptr` uses a `Deleter` parameter at construction (defaulted if not present) so that you could have one for the array case.
Matthieu M.
One thing that you'd want from a pointer-to-array is `operator[]`. And then `shared_array<int> p(new int[10])` may be a convenient shorthand for `shared_ptr<int> p(new int[10], array_deleter<int>());` even though it might well use `shared_ptr` with a deleter under the hood. Generally I think there's a big difference between having a single object and an array: if a function takes a `shared_ptr<T>`, will it also handle dynamic arrays?
visitor
this way makes the most sense to me, i just dont like the idea of making 2 separate classes :D lazy me
Marlon
@Marlon: You can get around having to make 2 separate classes by using 3 (inheriting common functionality, which they should have a lot from a common base) :)
UncleBens
A: 

I think the real solution is to get rid of your own autopointer class and to get rid of the use of C-style arrays. I know this has been said many, many times before, but there really isn't much point in using C-style arrays any more. Just about everything you can do with them can be done using std::vector or with boost::array. And both of these create distinct types, so you can overload on them.

anon
+1  A: 

new[] is specifically defined to have pointer value despite the array-to-pointer implicit conversion that would kick in anyway.

But I don't think you're out of luck. After all, your example isn't managing a pointer to an int, it's managing a pointer to an int[10]. So the ideal way is

MyAutoPtr<int[10]> ptr2(new int[10]);

As Red-Nosed Unicorn mentions, new int[10] does not create a C-style array. It will if your compiler complies to the C standard as well, but C++ allows C-style arrays to be more than C-style arrays in C. Anyway, new will create you a C-style array if you ask like this:

MyAutoPtr<int[10]> ptr2(new int [1] [10]);

Unfortunately, delete contents; will not work even with int (*contents)[10];. The compiler is allowed to do the right thing: the standard doesn't specify that the array is converted to a pointer as with new, and I believe I recall GCC substituting delete[] and emitting a warning. But it's undefined behavior.

So, you will need two destructors, one to call delete and one to call delete[]. Since you can't partially specialize a function, the functionality demands a partially specialized helper

template< class T > struct smartptr_dtor {
    void operator()( T *ptr ) { delete ptr; }
};

template< class T, size_t N > struct smartptr_dtor< T[N] > {
    void operator()( T (*ptr) [N] ) { delete [] ptr; }
};

template< class T >
void proper_delete( T *p ) {
    smartptr_dtor< T >()( p );
}

which for some reason I just subjected myself to ;v)

Unfortunately, this doesn't work with dynamic-sized arrays, so I'm going to write up another answer.

Potatoswatter
@Potatocorn: `new int[10]` does *not* create an `int[10]`. While the code you’ve written can be made to compile, it’s meaningless. There is no connection between the template type and the constructor value.
Konrad Rudolph
@Red: How is the result of `new T[10]` not a pointer to `T[10]`? The return type is `T*` but that simply does not reflect the type of the object in memory.
Potatoswatter
5.3.4/1: "If the entity is a non-array object, the new-expression returns a pointer to the object created. If it is an array, the new-expression returns a pointer to the initial element of the array."
Potatoswatter
@Potatocorn: `T[N]` is a strange beast in C++ and I can’t answer the question philosophically. But practically, there’s a simple reason: you cannot assign `new int[N]` to `T[N]`. (GCC says `incompatible types in assignment of 'int*' to 'int [10]'`). Subverting the type system in this point produces UB. Also, there’s no such thing as “type in memory”. In memory, all bytes are equal. Types only matter for the compiler.
Konrad Rudolph
@Potatocorn: 5.3.4/1 simply doesn’t apply here. Yes, `new T[N]` creates an array. But not a C-style array of type `T[N]`.
Konrad Rudolph
@Red: Hmm, but `new T [1][N]` does create and return the correct type, `T(*)[N]`. Now I just have to contend with the incomplete type of `T(*)[]`, which clearly cannot refer to an actual object but is yet useful as a template parameter.
Potatoswatter
A: 

Second attempt…

It's quite easy to make a smart pointer class smart about arrays. As you suspected, you don't need a runtime flag or argument to the constructor if you know it's an array to begin with. The only problem is that new and new[] have identical return types, so they cannot pass this information to the smart pointer class.

template< class T, bool is_array = false >
struct smartptr {
    T *storage;

    smartptr( T *in_st ) : storage( in_st ) {}

    ~smartptr() {
        if ( is_array ) delete [] storage; // one of these
        else delete storage; // is dead code, optimized out
    }
};

smartptr< int > sp( new int );
smartptr< int, true > sp2( new int[5] );

An alternative to the bool flag is to overload the meaning of T[] as Visitor mentions std::unique_ptr does in C++0x.

template< class T >
struct smartptr {
    T *storage;

    smartptr( T *in_st ) : storage( in_st ) {}

    ~smartptr() { delete storage; }
};

template< class T > // partial specialization
struct smartptr< T [] > {
    T *storage; // "T[]" has nothing to do with storage or anything else

    smartptr( T *in_st ) : storage( in_st ) {}

    ~smartptr() { delete [] storage; }
};

smartptr< int > sp( new int );
smartptr< int[] > sp2( new int[5] );
Potatoswatter
+1  A: 

It is not possible since new int[X] yields a pointer to the initial element of the array. It has the same type as int*.

One of the common solutions is to use deleters. Add one more template argument to your class so you could pass custom deleter for your pointer. It'll make your class more universal. You could create default deleter like the following:

struct default_deleter
{
    template<typename T>
    void operator()( T* aPtr ) { delete aPtr; }
};

And for arrays you could pass custom deleter:

struct array_deleter
{
    template<typename T>
    void operator()( T* aPtr ) { delete[] aPtr; }
};

The simplest implementation will be:

template <typename T, typename D>
class MyAutoPtr
{
public: 
    MyAutoPtr(T* aPtr, D deleter = default_deleter() ) : ptr_(aPtr), deleter_(deleter) {};
    ~MyAutoPtr() { deleter_(ptr_); }
protected:
    D deleter_;
    T* ptr_;
};

Then you could use it as follows:

MyAutoPtr<int, array_deleter> ptr2(new int[10], array_deleter() );

You could make your class more complex so it could deduce type for deleter.

Kirill V. Lyadvinsky