Есть ли способ использовать специализацию шаблона для отделения нового от нового []? - PullRequest
14 голосов
/ 07 апреля 2010

У меня есть класс автоматического указателя, и в конструкторе я передаю указатель.Я хочу иметь возможность отделить новое от нового [] в конструкторе, чтобы я мог правильно вызвать delete или delete [] в деструкторе.Это можно сделать через специализацию шаблона?Я не хочу вводить логическое значение в конструкторе.

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

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

Ответы [ 7 ]

7 голосов
/ 07 апреля 2010

К сожалению, нет. Оба возвращают один и тот же тип, T*. Рассмотрите возможность использования функций компоновщика, которые вызывают соответствующий перегруженный конструктор:

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);
}

Теперь вы можете создавать объекты следующим образом:

MyAutoPtr<int> ptr = make_ptr<int>();
MyAutoPtr<int> ptr2 = make_ptr<int>(10);
3 голосов
/ 07 апреля 2010

std::unique_ptr в C ++ 0x будет иметь специализацию для динамических массивов, как показано ниже. Однако задачей пользователя будет создание экземпляра соответствующего экземпляра. На уровне языка нет способа отличить один указатель от другого.

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]);
}

Кроме того, может быть не очень хорошо загружать один класс с такими различными задачами. Например, повышение имеет shared_ptr и shared_array.

2 голосов
/ 08 апреля 2010

Это невозможно, поскольку new int[X] возвращает указатель на начальный элемент массива.Он имеет тот же тип, что и int*.

. Одним из распространенных решений является использование удалителей .Добавьте еще один аргумент шаблона в ваш класс, чтобы вы могли передать пользовательское средство удаления для вашего указателя.Это сделает ваш класс более универсальным.Вы можете создать средство удаления по умолчанию, как показано ниже:

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

А для массивов вы можете передать пользовательское средство удаления:

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

Самая простая реализация будет:

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_;
};

Тогда вы можете использовать его следующим образом:

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

Вы можете сделать свой класс более сложным, чтобы он мог выводить тип для средства удаления.

2 голосов
/ 07 апреля 2010

Я думаю, что реальное решение состоит в том, чтобы избавиться от вашего собственного класса autopointer и избавиться от использования массивов в стиле C. Я знаю, что это было сказано много, много раз прежде, но на самом деле нет особого смысла в использовании массивов в стиле C. Почти все, что вы можете с ними сделать, можно сделать с помощью std::vector или boost::array. И оба они создают разные типы, так что вы можете перегружать их.

2 голосов
/ 07 апреля 2010

С другой стороны, вы можете использовать определенную функцию make.

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

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

Конечно, это означает, что у вас есть соответствующая логика, но она инкапсулирована. Вы также можете добавить перегрузку, взяв T для копирования объекта, переданного во вновь созданный указатель и т. Д. *

Наконец, это также может быть сделано с перегрузками конструктора ... смысл не в том, чтобы вызывать new снаружи.

1 голос
/ 08 апреля 2010

Вторая попытка…

Довольно просто сделать умный класс указателей умным для массивов. Как вы и подозревали, вам не нужен флаг или аргумент времени выполнения для конструктора, если вы знаете, что это массив для начала. Единственная проблема заключается в том, что new и new[] имеют идентичные типы возврата, поэтому они не могут передать эту информацию в класс интеллектуальных указателей.

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] );

Альтернативой флагу bool является перегрузка значения T[], как упоминает посетитель std::unique_ptr в 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] );
1 голос
/ 07 апреля 2010

new[] специально определено для того, чтобы иметь значение указателя, несмотря на неявное преобразование массива в указатель, которое в любом случае вылетело бы.

Но я не думаю, что тебе не повезло. В конце концов, ваш пример не управляет указателем на int, он управляет указателем на int[10]. Итак, идеальный способ это

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

Как упоминает Red-Nosed Unicorn, new int[10] не создает массив в стиле C. Это будет, если ваш компилятор также соответствует стандарту C, но C ++ допускает, чтобы массивы в стиле C были больше, чем массивы в стиле C в C . В любом случае, new создаст вам массив в стиле C, если вы спросите так:

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

К сожалению, delete contents; не будет работать даже с int (*contents)[10];. Компилятору разрешено делать правильные вещи: стандарт не указывает, что массив преобразуется в указатель, как при new, и я полагаю, что я помню GCC, подставив delete[] и выдав предупреждение. Но это неопределенное поведение.

Итак, вам понадобятся два деструктора, один для вызова delete и один для вызова delete[]. Поскольку вы не можете частично специализировать функцию, функциональность требует частично специализированного помощника

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 );
}

, по какой-то причине я только что подвергся; v)

К сожалению, это не работает с массивами динамического размера, поэтому я собираюсь написать другой ответ.

...