Существует ли способ использовать шаблонную специализацию для разделения новый от нового []?

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

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

// in use:
MyAutoPtr<int> ptr(new int);
MyAutoPtr<int> ptr2(new int[10]);
14
задан Marlon 7 April 2010 в 08:19
поделиться

7 ответов

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 .

3
ответ дан 1 December 2019 в 13:33
поделиться

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

Довольно легко сделать класс интеллектуального указателя умным для массивов. Как вы и подозревали, вам не нужен флаг времени выполнения или аргумент конструктора, если вы знаете, что это массив для начала. Единственная проблема заключается в том, что 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
ответ дан 1 December 2019 в 13:33
поделиться

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

2
ответ дан 1 December 2019 в 13:33
поделиться

К сожалению, нет. Оба возвращают один и тот же тип, 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);
7
ответ дан 1 December 2019 в 13:33
поделиться

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

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

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

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

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

2
ответ дан 1 December 2019 в 13:33
поделиться

Это невозможно, поскольку 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
ответ дан 1 December 2019 в 13:33
поделиться

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

К сожалению, удалить содержимое; не будет работать даже с 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)

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

1
ответ дан 1 December 2019 в 13:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: