Форум программистов, компьютерный форум CyberForum.ru

realloc и вызов конструктора - C++

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 153, средняя оценка - 4.65
pito211
 Аватар для pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
20.05.2011, 13:52     realloc и вызов конструктора #1
здраствуйте! мне препод сказал, что можно выделить память оператором new, а потом довыделить её с помощью realloc и каким-то образом вызвать конструкторы для новой памяти(ну или это я так его понял). Как это можно сделать?

задание вобще такое:
Во всех вариантах необходимо первоначально создать шаблон класса для работы с массивом произвольного типа данных. Шаблон должен включать:
указатель, хранящий адрес размещения массива в динамической памяти;
целочисленную переменную, показывающую количество занятых элементов массива;
конструктор без параметров, создающий динамический массив заданного типа, с нулевым количеством занятых элементов;
конструктор копирования;
очистку массива;
метод «обработки массива»;
деструктор.
Далее на основе данного шаблона создать класс для работы со строкой символов, специализировав метод «обработки массива» для вашей конкретной задачи.
я хотел класс allocator использовать, но нельзя((. Пока на примете только один вариант - выделять с помощью new и как только память заполниться выделять участок в два раза крупнее копировать старые данные и подчищать за ними. Есть ещё варианты?
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
pito211
 Аватар для pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
21.05.2011, 07:57  [ТС]     realloc и вызов конструктора #21
посмотрите, что я нашёл: http://ru.wikipedia.org/wiki/New_(C%2B%2B)

а именно следующее:

Placement new

Существует особая форма оператора new, называемая Placement new. Данный оператор не выделяет память, а получает своим аргументом адрес на уже выделенную каким-либо образом память (например, на стеке или через malloc). Происходит размещение (инициализация) объекта путем вызова конструктора, и объект создается в памяти по указанному адресу.
видимо это мой препод и имел ввиду. Странно, что никто не упоминул этот вариант, я правда свою прогу уже переписывать не буду, но так как это относится к теме, то я нашёл более подробную статью:
[11.10] Что такое "синтаксис размещения" new ("placement new") и зачем он нужен?

Есть много случаев для использования синтаксиса размещения для new. Самое простое - вы можете использовать синтаксис размещения для помещения объекта в определенное место в памяти. Для этого вы указываете место, передавая указатель на него в оператор new:


#include <new> // Необходимо для использования синтаксиса размещения
#include "Fred.h" // Определение класса Fred

void someCode()
{
char memory[sizeof(Fred)]; // #1
void* place = memory; // #2

Fred* f = new(place) Fred(); // #3 (смотрите "ОПАСНОСТЬ" ниже)
// Указатели f и place будут равны

// ...
}
В строчке #1 создаётся массив из sizeof(Fred) байт, размер которого достаточен для хранения объекта Fred. В строчке #2 создаётся указатель place, который указывает на первый байт массива (опытные программисты на С наверняка заметят, что можно было и не создавать этот указатель; мы это сделали лишь чтобы код был более понятным [As if - YM]). В строчке #3 фактически происходит только вызов конструктора Fred::Fred(). Указатель this в конструкторе Fred будет равен указателю place. Таким образом, возвращаемый указатель тоже будет равен place.

СОВЕТ: Не используйте синтаксис размещения new, за исключением тех случаев, когда вам действительно нужно, чтобы объект был размещён в определённом месте в памяти. Например, если у вас есть аппаратный таймер, отображённый на определённый участок памяти, то вам может понадобиться поместить объект Clock по этому адресу.

ОПАСНО: Используя синтаксис размещения new вы берёте на себя всю ответственность за то, что передаваемый вами указатель указывает на достаточный для хранения объекта участок памяти с тем выравниванием (alignment), которое необходимо для вашего объекта. Ни компилятор, ни библиотека не будут проверять корректность ваших действий в этом случае. Если ваш класс Fred должен быть выровнен четырёхбайтовой границе, но вы передали в new указатель на не выровненный участок памяти, у вас могут быть большие неприятности (если вы не знаете, что такое "выравнивание" (alignment), пожалуйста, не используйте синтаксис размещения new). Мы вас предупредили.

Также на вас ложится вся ответственность по уничтожения размещённого объекта. Для этого вам необходимо явно вызвать деструктор:


void someCode()
{
char memory[sizeof(Fred)];
void* p = memory;
Fred* f = new(p) Fred();
// ...
f->~Fred(); // Явный вызов деструктора для размещённого объекта
}
Это практически единственный случай, когда вам нужно явно вызывать деструктор.
спасибо сайту http://faqs.org.ru/progr/c_cpp/cpp_lite3.htm и конечно google.com
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
Kastaneda
Модератор
Эксперт С++
 Аватар для Kastaneda
4248 / 2780 / 219
Регистрация: 12.12.2009
Сообщений: 7,109
Записей в блоге: 1
Завершенные тесты: 1
21.05.2011, 10:00     realloc и вызов конструктора #22
pito211, если интересно, скачай "Философия С++" 1-ый том. Стр.414-422. Там очень подробно расписанно про то, что ты нашел.
ForEveR
Модератор
Эксперт С++
 Аватар для ForEveR
7955 / 4717 / 318
Регистрация: 24.06.2010
Сообщений: 10,525
Завершенные тесты: 3
21.05.2011, 10:56     realloc и вызов конструктора #23
pito211, placement new - хорошая вещь, да. Но используется редко все же.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16946 / 5351 / 329
Регистрация: 30.03.2009
Сообщений: 14,389
Записей в блоге: 26
21.05.2011, 11:00     realloc и вызов конструктора #24
pito211, placement new в твоём случае принципиально не отличается. Ибо что вызов new, что вызов malloc + new дадут по сути один и тот же результат. При этом на пару с realloc этот placement new всё равно работать толком не будет. В том смысле, что ничего нового не даст. malloc/realloc - интерфейсы для выделения plain-памяти, которая НЕ трактуется как массив объектов. Т.е. если ты вызвал realloc, то данные от объектов у тебя скопируются, но они скопируются как образ памяти, но не как объекты, потому что никаких операций присваивания или конструкторов вызываться не будет (хотя новые объекты как бы появились). Такая работа может послужить дополнительным источником ошибок
ValeryLaptev
Эксперт С++
1012 / 791 / 46
Регистрация: 30.04.2011
Сообщений: 1,601
21.05.2011, 11:29     realloc и вызов конструктора #25
Вот кой-какой материал по этой теме

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
Листинг 9.1. Интерфейс динамического массива с изменяемым размером
template<typename T> 
class TArray {
public:
  // типы
  typedef T                                     value_type;            
  typedef T*                                    iterator;              
  typedef const T*                              const_iterator;        
  typedef T&                                    reference;             
  typedef const T&                              const_reference;       
  typedef std::size_t                           size_type;             
  // конструкторы/копирование/деструктор
  TArray(const size_type& n = minsize);
  TArray(const TArray<T>& array);
  template <class Iterator> TArray(Iterator first, Iterator last);
  ~TArray() { delete [] elems; elems = 0; }
  Tarray<T>& operator=(const TArray<T>&);
  template<typename U> TArray& operator=(const TArray<U>&);
// итераторы
  iterator begin() { return elems; }
  const_iterator begin() const { return elems; }
  iterator end() { return elems+Count; }
  const_iterator end() const { return elems+Count; }
// размеры
  size_type size() const                // длина массива
  { return Count; }
  bool empty() const                    // есть ли элементы
  { return (Count == 0); }
  size_type capacity() const            // потенциальный размер
  { return Size; }
  void resize(size_type newsize);       // изменить размер
// доступ к элементам
  reference operator[](size_type)
  { rangecheck(i);                      // проверка индекса
    return elems[i]; 
  }
  const_reference operator[](size_type) const 
  { rangecheck(i);                      // проверка индекса
    return elems[i]; 
  }
  reference front() { return elems[0]; }
  const_reference front() const { return elems[0]; }
  reference back() { return elems[size()-1]; }
  const_reference back() const { return elems[size()-1]; }  
// методы-модификаторы
  void push_back(const T& v);
  void pop_back()                        // удалить последний элемент
  { if (!empty()) --Count; 
    else throw std::domain_error("array<>: empty array!");
  }
  void clear() { Count = 0; }           // очистить массив
  void swap(TArray<T>& other)           // обменять с другим массивом
  {  std::swap(elems, v.elems);         // стандартная функция обмена
     std::swap(Size, v.Size);
     std::swap(Count, v.Count);
  }
  void assign(const T& v)               // заполнить массив
  { if (!empty()) 
      for(size_type i = 0; i < Count; ++i) 
          elems[i] = v;
  } 
private:
  static const size_type minsize = 10;  // минимальный размер массива
  size_type Size;                       // выделено элементов в памяти
  size_type Count;                      // количество элементов
  value_type * elems;                   // указатель на данные
// проверка индекса
    void rangecheck (size_type i) 
    { if (i >= size()) 
        throw std::range_error("array<>: index out of range");
    }
};
// обмен – внешняя функция
template<typename T> void swap(TArray<T>&, TArray<T>&)
inline void swap(TArray<T>& x, TArray<T>& y)
{ x.swap(y); }
// сравнения
template<typename T> 
bool operator==(const TArray<T>& x, const TArray<T>& y)
{ if (x.size() == y.size())
  { for(size_type i = 0; i < x.size(); ++i) 
        if (x[i]!=y[i]) return false;
    return true;
  }
  else return false;
}
template<typename T> 
bool operator!=(const TArray<T>& x, const TArray<T>& y)
{ return !(x==y); }
Такой массив называется растущим, так как элементы добавляются только к его концу: массив «растет». В начале класса-шаблона, как обычно, заданы определения типов. Заменив double на любой другой встроенный числовой тип, получим реализацию динамического массива для другого типа. Реализацию методов при этом переписывать не требуется, поскольку они реализованы в терминах объявленных типов.

Большинство методов очень просты, поэтому реализованы непосредственно в классе. Операции доступа по индексу используют для проверки индекса приватную функцию rangecheck()
.
В классе три поля: указатель на выделенную память elems, поле Size определяет количество зарезервированных элементов, а поле Count содержит количество присутствующих в массиве элементов. Очевидно, что поле Count увеличивается по мере добавления элементов в массив. Как только значение поля Count сравняется со значением поля Size, необходимо выделять новую память.

Последовательный доступ к элементам динамического массива осуществляется итератором, который просто является указателем соответствующего типа. Поэтому в шаблоне не определен класс для итератора . Методы, принимающие параметры-итераторы или выдающие итератор-результат фактически работают с указателями на элементы массива — их реализация очень проста. Конечно, это снижает безопасность нашего кода, но в данном случае нам важнее разобраться с управлением памятью.

Класс массива обеспечивает получение начального (методы begin()) и конечного (методы end()) значения итератора, получение значения первого (методы front()) и последнего (методы back()) элемента. Конечное значение итератора, как обычно, — за последним элементом массива. Перемещение по элементам и получение значения элемента выполняется операциями с указателями (операции инкремента, декремента и разыменования).
Реализация остальных методов представлена в листинге 9.2.

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
Листинг 9.2. Реализация методов шаблона TArray
// конструкторы
template <class T>
TArray<T>::TArray(const TArray<T> &t)
:Size(t.Size), 
 Count(t.Count),
 elems(new T[Size])                         // новый массив
{   for(size_type i = 0; i<Count; ++i)      // копируем
       elems[i] = t.elems[i];
}
template <class T>
TArray<T>::TArray(const size_type& n)
:Size((n > minsize)?n:minsize), 
 Count(n),                                  // массив пустой
 elems(new T[Size])                         // новый массив
{ for(size_type i = 0; i<Size; ++i)         // обнуляем
     elems[i] = T();
}
template <class T>
template <class Iterator>
TArray<T>::TArray(Iterator begin, Iterator end)
{ if (!(begin > end))
  { Size  = (end - begin);                  // количество элементов
    Count = Size;                           // текущий размер
    elems = new T[Size];                    // создаем массив
    for(size_type i = 0; i<Count; ++i)      // заполняем массив
       elems[i] = *(begin+i);               // копируем из массива
  }
  else                                      // неправильные параметры
  throw std::invalid_argument("array<>: invalid_argument (begin > end)!");
}
// добавление элементов
template <class T>
void TArray<T>::push_back(const value_type& v)
{ if (Count == Size)                        // места нет
    resize(Size * 2);                       // увеличили «мощность»
  elems[Count++] = v;                       // присвоили
}
template <class T>
void TArray<T>::resize(size_type newsize)
{ if (newsize > capacity())
  { T *data = new T[newsize];               // новый массив
    for(size_type i = 0; i<Count; ++i)      // копируем
       data[i] = elems[i];
    delete[] elems;                         // возвращаем память
    elems = data;                           // вступили во владение
    Size = newsize;                         // «увеличили «мощность»
  }
}
template <class T>
template <class U>
TArray<T>& TArray<T>::operator=(const TArray<U>& other)
{ TArray<T>tmp(other.begin(), other.end());
  tmp.resize(other.capacity());
  swap(tmp);
  return *this;
}
template <class T>
TArray<T>& TArray<T>::operator=(const TArray<T>& other)
{ TArray<T>tmp(other.begin(), other.end());
  tmp.resize(other.capacity());  
  swap(tmp);
  return *this;
}
Если что непонятно - спрашивайте.
pito211
 Аватар для pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
21.05.2011, 12:22  [ТС]     realloc и вызов конструктора #26
Цитата Сообщение от Evg Посмотреть сообщение
pito211, placement new в твоём случае принципиально не отличается. Ибо что вызов new, что вызов malloc + new дадут по сути один и тот же результат. При этом на пару с realloc этот placement new всё равно работать толком не будет. В том смысле, что ничего нового не даст. malloc/realloc - интерфейсы для выделения plain-памяти, которая НЕ трактуется как массив объектов. Т.е. если ты вызвал realloc, то данные от объектов у тебя скопируются, но они скопируются как образ памяти, но не как объекты, потому что никаких операций присваивания или конструкторов вызываться не будет (хотя новые объекты как бы появились). Такая работа может послужить дополнительным источником ошибок
если я правильно понимаю действие реаллока, то в некоторых случаях он вернёт ссылку на новую область памяти(пустую наверно, раз консттрукторов копирования он не вызывает?), а в некоторых расширит существующую, если это возможно?

A pointer to the reallocated memory block, which may be either the same as the ptr argument or a new location.
The type of this pointer is void*, which can be cast to the desired type of data pointer in order to be dereferenceable.
If the function failed to allocate the requested block of memory, a NULL pointer is returned, and the memory block pointed to by argument ptr is left unchanged.
и если это так, то это значит же что в некоторых случаях копировать не придётся, а это ведь серьёзное увеличение производительности(если вдруг не придётся тыщу элементов копировать)?
ValeryLaptev
Эксперт С++
1012 / 791 / 46
Регистрация: 30.04.2011
Сообщений: 1,601
21.05.2011, 12:26     realloc и вызов конструктора #27
Цитата Сообщение от pito211 Посмотреть сообщение
и если это так, то это значит же что в некоторых случаях копировать не придётся, а это ведь серьёзное увеличение производительности(если вдруг не придётся тыщу элементов копировать)?
По поводу увеличения памяти - посмотрите в моем посте функцию reduce(). Вы увидите, что каждое увеличение - в 2 раза. Это не с потолка. Это как раз для того, чтобы минимизировать переписывание. Эти вопросы подробно обсуждаются Гербом Саттером в его книжках.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16946 / 5351 / 329
Регистрация: 30.03.2009
Сообщений: 14,389
Записей в блоге: 26
21.05.2011, 14:22     realloc и вызов конструктора #28
Цитата Сообщение от pito211 Посмотреть сообщение
если я правильно понимаю действие реаллока, то в некоторых случаях он вернёт ссылку на новую область памяти(пустую наверно, раз консттрукторов копирования он не вызывает?), а в некоторых расширит существующую, если это возможно?
Да, если возможно, то realloc растянет существующую память. Если нужно выделить новую, то выделит новую и скопирует старые данные

Цитата Сообщение от pito211 Посмотреть сообщение
и если это так, то это значит же что в некоторых случаях копировать не придётся, а это ведь серьёзное увеличение производительности(если вдруг не придётся тыщу элементов копировать)?
Всё это хорошо для POD-типов. Не помню как расшифровывается, но это плоские типы, содержащие только данные, которые можно копировать побайтно. С классами в общем случае так поступать нельзя, потому что у класса может быть переопределён оператор присваивания. При выделении памяти по новой номинально рождаются новые объекты (а потому должен быть вызван конструктор) и уничтожаются старые объекты (при этом должен быть вызван деструктор). Realloc ничего этого не делает. Более того, после того, как отработал realloc, уже некому хотя бы смоделировать такое поведение
ValeryLaptev
Эксперт С++
1012 / 791 / 46
Регистрация: 30.04.2011
Сообщений: 1,601
25.05.2011, 23:24     realloc и вызов конструктора #29
Вот дополнительный материал по управлению памятью. Дам в нескольких ответах - много материала.

Свободная память и куча
Динамическая память выделяется объектам во время выполнения программы с помощью специальных операций new и new[][1-5.3.4]. Возврат динамической памяти (и уничтожение объектов при этом) тоже выполняются специальными операциями delete и delete[] [1-5.3.5]. В стандарте [1-12.5] такая динамическая память называется «свободной» памятью — мы ниже рассмотрим подробности работы с ней.

Однако С++ включает и другой вид динамической памяти, о котором в стандарте явно не сказано. Дело в том, что в С++ по наследству от С остались функции malloc(), calloc(), realloc() и free() [1-20.4.6], которые тоже работают с динамической памятью. Первые три из них различными способами выделяют динамическую память, а последняя, естественно, освобождает. Герб Саттер [21] называет динамическую память, с которой работают функции malloc()/free() кучей, как она и называется в стандарте С99 [59].

Стандарт С++ не определяет, должны ли быть реализованы операции new/delete с помощью этих функций. Однако в стандарте явно говорится [1-20.4.6/3/4], что функции malloc()/free() не должны быть реализованы с помощью операций new/delete. Поэтому в общем случае свободная память, с которой работают операции new/delete, и динамическая память, с которой работают функции malloc()/free(), — это «две большие разницы». Возможно, в каком-нибудь компиляторе эти два вида динамической памяти и совпадают, но полагаться на это ни в коем случае нельзя.
Отсюда следует совершенно однозначный вывод: никогда не «смешивайте» эти два механизма управления динамической памятью в своих программах. Этот совет достоин того, чтобы повторить его еще раз!

СОВЕТ
Никогда не «смешивайте» два механизма управления динамической памятью в своих программах.

Память, выделенная одной из операций new, должна возвращаться системе только соответствующей операцией delete, а память, выделенная одной из функций malloc(), calloc(), или realloc(), должна возвращаться только с помощью функции free().

Добавлено через 6 минут
Размещающий new
Существует еще одна форма операции new, которая позволяет разделить «во времени и пространстве» операции выделения памяти и конструирования объекта в этой памяти. Эта форма называется «размещающий» new (placement new) и тоже работает только при подключении заголовка
C++
1
#include <new>
В стандарте [1-18.4] этот вид операции определен так:
C++
1
2
void *operator new(std::size_t size, void *ptr) throw();
void *operator new[](std::size_t size, void *ptr) throw();
Как видите, эта форма оператора тоже исключений не вызывает. Второй аргумент — это указатель на уже выделенный каким-нибудь образом буфер памяти. Действие этой операции похоже на union, но «склеивание» адресов выполняется во время работы программы, а не при компиляции.
В стандарте определены и соответствующие функции delete:
C++
1
2
void operator delete(void* ptr, void*) throw();
void operator delete[](void* ptr, void*) throw();
Однако явным образом их вызывать не требуется.
Рассмотрим несколько простых примеров, чтобы было понятны способы применения этого вида операции new.
C++
1
2
3
4
5
6
int *i = new int[10];                   // выделели память
i[0] = 21;   cout << i[0] << endl;
int *j = new(i) int[10];                // placement new
cout << j[0] << endl;                   // j[0] == i[0] 
j[0] = 22;   cout << i[0] << endl;      // i[0] == j[0]
delete [] i;                            //
Здесь сначала создается динамический массив i, и нулевому элементу присваивается значение 21. Затем размещающий new «приклеивает» по этому же адресу другой динамический массив j. После этого любые изменения в массиве i синхронно изменяют массив j и наоборот. Обратите внимание — у нас два указателя на одну и ту же память, а не два разных массива! Поэтому удалять такой динамический массив нужно только один раз.
Вместо динамического массива мы вполне можем использовать обычный массив, например
C++
1
2
3
4
int i[10] = { 1,2,3,4,5,6,7,8,9,0};     // выделели память
int *j = new(i) int[10];                // placement new
cout << j[0] << endl;                   // j[0] == i[0] 
j[0] = 22;   cout << i[0] << endl;      // i[0] == j[0]
В этом случае размещаемый массив j удалять нельзя. Однако компилятор опять не выдаст никакого сообщения, если программист нечаянно напишет оператор удаления, например
C++
1
delete[] j;
При выполнении опять же может случиться все, что угодно.
Исходный объект и размещаемый, конечно, могут быть разного типа и размера. Но тут нужно быть осторожным, так как компиляторы просто не выдают никаких предупреждающих сообщений .
C++
1
int m; char *pm = new(&m) char;
Этот случай безопасен, так как размещаемая величина меньше по размеру, чем «база». А вот такой вариант
C++
1
char t; double *pt = new(&t) double();
чреват неприятностями. При компиляции сообщений никаких не выдается, поэтому во время работы может произойти все, что угодно. И тут уж как повезет.
Отсутствуют сообщения о преобразовании типов и в случае использования массивов:
C++
1
2
3
int tt[4]={0};  double *p = new(tt)double[4];
double *t = new double[4];  int *j = new (t) int[4];
double m[4];  j = new (m) int[4];
Обратите внимание — в размещающем new в этом случае не нужно указывать операцию взятия адреса &, так как имя массива уже по умолчанию является адресом первого элемента. Конечно, можно «установиться» на любой элемент массива:
C++
1
double *p = new(&tt[1])double[4];
Естественно, в этом случае нужно задавать адрес элемента.
Отсутствие сообщений о том, что типы разные, может спровоцировать ошибки, например:
C++
1
p[0] = 1.2;  cout << p[0] << endl;  cout << tt[0] << endl;
В этом фрагменте мы меняем значение элемента массива типа double, размещенное поверх целого массива. В результате меняется и значение элемента целого массива. Ситуация совершенно аналогична тому, что происходит при использовании union.
Можно поверх скалярной величины разместить массив, и поверх массива — скалярную величину, например
C++
1
2
3
4
5
6
7
int m; char *pm = new(&m) char[sizeof(int)];
char rr[sizeof(int)]; int *ptt = new (rr) int(50); 
Сравните это с объявлением union
union A
{   int m;
    char pm[sizeof(int)];
};
В обоих случаях происходит размещение целой переменной m и символьного массива pm по одному адресу, однако размещаемый new это выполняет во время работы программы, а при использовании union — это делает компилятор.
В принципе, использование размещаемого new в этих случаях эквивалентно использованию обычного указателя, например
C++
1
int *j = &i[0];
Однако такая запись менее заметна (и поэтому провоцирует больше ошибок), чем использование размещающего new.

Размещающий new обычно применяется как раз не для встроенных типов. Еще раз напомним, что работа «обычного» new состоит из двух операций: выделение памяти и вызов конструктора для конструирования объекта. Память размещающий new не выделяет, но вызов конструктора выполняется и в этом случае. Но тогда возникает проблема вызова деструктора — для «обычных» динамических объектов деструктор вызывался автоматически операцией delete. Однако для размещающего new операцию delete выполнять не нужно, поэтому и деструктор автоматически не вызывается!
Решение очень простое: нужно вызвать деструктор явно [1-12.4/13]. Рассмотрим пример (листинг 8.5).
C++
1
2
3
4
5
6
7
8
9
10
11
Листинг 8.5. Явный вызов деструктора
class Integer                           // некоторый класс
{ int i;
  public: 
    Integer() { cout << "constructor" << endl; }
    ~Integer() { cout << "destructor" << endl; }
};
// …
int m;                              // память для объекта
Integer *pi = new(&m) Integer;      // размещение объекта
pi->~Integer();                     // разрушение объекта
Мы объявили простой класс, размер которого равен размеру целого. Размещение делается обычным способом. А последняя строка как раз и является явным вызовом деструктора. Обратите внимание, что и в данном случае преобразования типов не требуется.
Заменим в классе Integer конструктор по умолчанию конструктором инициализации:
C++
1
Integer(int k=0):i(k) { }
Тогда можно задавать размещение объекта с инициализацией, например
C++
1
Integer *pi = new(&m) Integer(6);
Если уж нам приходится «работать компилятором», то нужно помнить, что при уничтожении массива требуется вызывать деструктор для каждого его элемента.
C++
1
2
3
int tt[4]; 
pi = new(tt) Integer[4];                
for(int i = 0; i < 4; ++i) (pi+i)->~Integer();
При размещении массива типа Integer каждый из элементов инициализируется 0, поскольку вызывается конструктор инициализации с аргументом по умолчанию.
Вызов деструктора написан в указательном стиле. Можно использовать и привычный синтаксис с индексом, например
C++
1
for (int k = 0; k < 4; ++k) pi[k].~Integer();
До сих пор мы в качестве «базы» размещаемого new использовали только встроенные типы, однако это совершенно не обязательное условие — тип «основы» может быть любым. Например, определим небольшой шаблон
C++
1
2
3
4
template <class T, int size = sizeof(T)>
class DynamicUnion
{   char s[size];
};
При инстанцировании объект конкретного типа займет ровно столько байт памяти, каков размер типа T. Теперь мы можем использовать этот шаблон для размещения объектов типа Integer
C++
1
2
3
DynamicUnion<Integer> t;
Integer *pt = new(&t) Integer(7);
pt->~Integer();
Деструктор можно вызывать и так
C++
1
(*pt).~Integer();
Разрешается «разделить» запрос памяти и конструирование в ней объекта, например
C++
1
2
3
4
5
void *p = new DynamicUnion<Integer>;    // выделили sizeof(Integer) байт  
new (p) Integer(7);                     // разместили объект  
((Integer*)p)->~Integer();              // удалили объект
Оператор
new (p) Integer(7);                     // разместили объект
представляет собой «объявление» объекта типа Integer. Вызывается конструктор класса Integer и размещает поля объекта по указанному адресу, выполняя их инициализацию.
В данном случае при выделении памяти использован бестиповый указатель. Ни запрос памяти, ни размещение по этому адресу объекта не требуют преобразования типа. А вот разрушение объекта без преобразования указателя вызывает протесты компилятора — C++ Builder 6 выдает ошибку E2288:
C++
1
Pointer to structure required on left side of -> or ->*
Слева от -> или ->* требуется указатель на структуру
Поскольку р не является типизированным указателем на структуру, «возмущение» компилятора совершенно обосновано.
Точно так же с явным преобразованием типа необходимо обращаться к методам размещенного объекта. Пусть, например, в классе Integer определен метод:
C++
1
2
void print() 
{ cout << "print()="<< i << endl; }
Тогда обращение к нему должно выглядеть так:
C++
1
((Integer*)p)->print();
Если мы при запросе памяти объявим указатель типа Integer, то при обращении, естественно, преобразования не потребуется, например
C++
1
2
3
4
Integer *p = (Integer*)new DynamicUnion<Integer>;   // преобразование здесь
new (p) Integer(15);                                // размещение объекта
p->print();
p->~Integer();
Обратите внимание, что этом фрагменте мы имеем единственный указатель, получивший адрес объекта в динамической памяти. Можно обойтись вообще без динамической памяти, например
C++
1
2
int m; 
new(&m) Integer(6);
Объект типа Integer создан и проинициализирован по месту переменной m. Тогда возникает вопрос: как же тогда вызывать деструктор такого объекта? А вот так:
C++
1
((Integer*)&m)->~Integer();
или так
C++
1
(*(Integer*)&m).~Integer();
К сожалению, с массивами размещающий new не всегда работает правильно (хотя это могут быть недоработки конкретных компиляторов). Рассмотрим простейший пример
C++
1
2
3
int tt[4]; 
Integer *pi = new(tt) Integer[4];   // корректный вызов конструктора
new(tt) Integer[4];                 // некорректный вызов конструктора
Вариант с присвоением адреса указателю pi работает совершенно правильно, вызывая четыре конструктора со значением по умолчанию, равному 0. А вот второй вариант инициализирует первый элемент массива значением 4, а остальные — обнуляет.
Evg
Эксперт С++Автор FAQ
 Аватар для Evg
16946 / 5351 / 329
Регистрация: 30.03.2009
Сообщений: 14,389
Записей в блоге: 26
25.05.2011, 23:33     realloc и вызов конструктора #30
ValeryLaptev, не проще ли ссылку на статью выложить?

 Комментарий модератора 
Ссылка удалена как нарушающая п. 3.7 правил форума.
ValeryLaptev
Эксперт С++
1012 / 791 / 46
Регистрация: 30.04.2011
Сообщений: 1,601
25.05.2011, 23:48     realloc и вызов конструктора #31
Цитата Сообщение от Evg Посмотреть сообщение
ValeryLaptev, не проще ли ссылку на статью выложить?
Ну, во-первых, это не статья, а такой же пост, как и этот
К тому же там не совсем то, что здесь.
Во-вторых, это на другом сайте. А теперь - здесь есть.
Вон, чел в Вики статью видел, а на РСДН даже и не думал искать...
pito211
 Аватар для pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
26.05.2011, 09:17  [ТС]     realloc и вызов конструктора #32
спасибо!
realloc+(placement new) не катит, а жаль!
вобще я смотрел в моей VS 2008 там new реализован через malloc, и если бы я написал то оно нормально работало, но если кому то взбредёт в голову переписать new, то класс мой накроется медным тазом?
ValeryLaptev
Эксперт С++
1012 / 791 / 46
Регистрация: 30.04.2011
Сообщений: 1,601
26.05.2011, 09:19     realloc и вызов конструктора #33
Вообще-то о перегрузке new() - много материала. Тут самый лучший способ - перегружать new в конкретном классе, не трогая глобальный. Тогда ничего медным тазом не накрывается...
CheshireCat
Эксперт С++
2907 / 1235 / 78
Регистрация: 27.05.2008
Сообщений: 3,316
26.05.2011, 11:02     realloc и вызов конструктора #34
Цитата Сообщение от pito211 Посмотреть сообщение
мне препод сказал, что можно выделить память оператором new, а потом довыделить её с помощью realloc и каким-то образом вызвать конструкторы для новой памяти...
Мягко говоря, твой препод гонит пургу. Как он собирается потом освобождать эту память?
Наиболее приличный вариант написал Валерий Викторович Лаптев в посте 21.05.2011 11:29.
pito211
 Аватар для pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
26.05.2011, 14:22  [ТС]     realloc и вызов конструктора #35
Цитата Сообщение от CheshireCat Посмотреть сообщение
Мягко говоря, твой препод гонит пургу. Как он собирается потом освобождать эту память?
Наиболее приличный вариант написал Валерий Викторович Лаптев в посте 21.05.2011 11:29.
принципиально его resize не отличается от моего resize(тоже ведь копирует весь вектор) в посте 20.05.2011, 15:34. В любом случае спасибо, но в самом первом сообщении темы я спрашивал "о других вариантах". Если таковых нету, ну ладно, не судьба значит. Из всего что я прочитал я понял, что теоретически сделать это можно, если new реализован на базе malloc, но пологаться на это глупо. Частично ведь препод прав оказался? В моей VS 2008 new на базе malloc сделан, поэтому я сейчас пойду и попробую чисто ради интереса переделать resize и посмотреть чё получится. Заодно подумаю, как удалять. Если что то получится, то выложу что получилось, а если ничего не выложу, значит ничего не получилось

Добавлено через 2 часа 43 минуты
блин, а я не знал, что реаллок освобождает память, если блок передвинут на новое место. Тогда память пропадёт, жаль
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
26.05.2011, 15:43     realloc и вызов конструктора
Еще ссылки по теме:

C++ Вызов копирующего конструктора
C++ Вызов конструктора
C++ Вызов конструктора копий
C++ Вызов конструктора класса
C++ Вызов конструктора с аргументами

Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
CheshireCat
Эксперт С++
2907 / 1235 / 78
Регистрация: 27.05.2008
Сообщений: 3,316
26.05.2011, 15:43     realloc и вызов конструктора #36
Здесь дело даже не в этом. Присоединяюсь к мнению коллеги Evg:
Цитата Сообщение от Evg Посмотреть сообщение
Нельзя вперемешку использовать new/delete и malloc/realloc/free. Точнее можно, но с непредсказуемыми последствиями.
Стандарт языка гарантирует в этом случае UB - неопределенное поведение программы. А неопределенное поведение - оно такое неопределенное.... :-) программа имеет полное право в 99.9% случаев работать "как надо", а потом - отфигачить что-нибудь типа "format C:"

Если препод бестолковый из разряда "у меня работает, значит можно" - это очень плохо.
Yandex
Объявления
26.05.2011, 15:43     realloc и вызов конструктора
Ответ Создать тему
Опции темы

Текущее время: 05:23. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru