Есть ли способ использовать специализированную специализацию для ветки новых от новых []?
У меня есть класс auto pointer, а в конструкторе я передаю указатель. Я хочу, чтобы иметь возможность отделять новое от new [] в конструкторе, чтобы я мог правильно вызвать delete или delete [] в деструкторе. Можно ли это сделать с помощью специализированной специализации? Я не хочу передавать логическое значение в конструкторе.
template <typename T>
class MyAutoPtr
{
public:
MyAutoPtr(T* aPtr);
};
// in use:
MyAutoPtr<int> ptr(new int);
MyAutoPtr<int> ptr2(new int[10]);
Ответы
Ответ 1
std::unique_ptr
в С++ 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]);
}
Кроме того, может быть не так хорошо загружать один класс с такими различными задачами. Например, boost имеет shared_ptr
и shared_array
.
Ответ 2
К сожалению, нет. Оба возвращают один и тот же тип, 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
С другой стороны, вы можете использовать определенную функцию make
.
template <class T>
MyAutoPtr<T> make();
template <class T>
MyAutoPtr<T> make(size_t n);
Конечно, это означает, что у вас есть соответствующая логика, но она инкапсулирована. Вы также можете добавить перегрузку с помощью T
, чтобы скопировать объект, переданный в вновь созданный указатель и т.д.
Наконец, это также может быть сделано с перегрузками конструктора... точка не должна вызывать new
снаружи.
Ответ 4
Я думаю, что реальное решение состоит в том, чтобы избавиться от вашего собственного класса autopointer и избавиться от использования массивов C-стиля. Я знаю, что это было сказано много, много раз раньше, но на самом деле нет смысла использовать массивы C-стиля. Почти все, что вы можете сделать с ними, можно сделать с помощью std::vector
или с помощью boost::array
. И оба они создают разные типы, поэтому вы можете перегружать их.
Ответ 5
Это невозможно, так как 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() );
Вы можете сделать свой класс более сложным, чтобы он мог выводить тип для удаления.
Ответ 6
new[]
определенно имеет значение указателя, несмотря на неявное преобразование от массива к указателю, которое в любом случае ударило бы.
Но я не думаю, что тебе не повезло. В конце концов, ваш пример не управляет указателем на int
, он управляет указателем на int[10]
. Таким образом, идеальным способом является
MyAutoPtr<int[10]> ptr2(new int[10]);
Как упоминает Red-Nosed Unicorn, new int[10]
не создает массив C-style. Это будет, если ваш компилятор также соответствует стандарту C, но С++ позволяет массивам C-стиля быть больше, чем массивы C-стиля в C. В любом случае new
создаст вам массив C-стиля, если вы спросите вот так:
MyAutoPtr<int[10]> ptr2(new int [1] [10]);
К сожалению, delete contents;
не будет работать даже с int (*contents)[10];
. Компилятору разрешено делать правильные вещи: стандарт не указывает, что массив преобразуется в указатель, как в new
, и я считаю, что я помню, как GCC заменяет delete[]
и выдаёт предупреждение. Но это поведение undefined.
Итак, вам понадобятся два деструктора, один для вызова 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)
К сожалению, это не работает с массивами динамического размера, поэтому я напишу еще один ответ.
Ответ 7
Вторая попытка...
Легко сделать класс интеллектуального указателя умным по массивам. Как вы подозревали, для конструктора вам не нужен флаг или аргумент времени выполнения, если вы знаете его как массив. Единственная проблема заключается в том, что 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[]
, как упоминает Visitor std::unique_ptr
в С++ 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] );