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

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

Войти
Регистрация
Восстановить пароль
Другие темы раздела
C++ Что это за компонент? http://www.cyberforum.ru/cpp/thread300352.html
Всем привет. Кто ни будь, знает в какой библиотеке есть компоненты: TLight и TTrend ? Знакомы ли вообще такие названия кому ни будь?
C++ Visual C++ и Builder C++ сильно отличаются друг от друга? Доброго времени суток. У меня такой вопрос, Visual C++ и Builder C++ сильно отличаются друг от друга? Например если прога была написана в Visual, то будет ли она корректно работать в Builder? http://www.cyberforum.ru/cpp/thread300349.html
Комментарий в коде C++
как в С++ делать коментр?? в паскале так: { bla bla} а в С++ как???
Списки, динамическая память и указатели C++
Нужна помощь! Требуется написать функцию rearrangment( ), меняющую i-ю и j-ю строки текста. То бишь нужно создать однонаправленный список, в который заносится построчно текст какой-либо уже имеющейся программы, вывести исходный список и список, у которого указанные две строки меняются местами. Постскриптум: Borland C++ Постпостскриптум: вот метода -...
C++ Найти решение - на разные темы http://www.cyberforum.ru/cpp/thread298385.html
Доброго времени суток!!! Уважаемые форумчанины, требуется помощь в решении задач по С++. За 1,5 месяца не смог разобраться ни в одном компиляторе. У меня видимо руки не оттуда растут, или эти компиляторы слишком "умные". Под Линукс вообще не понял как компилировать. А у меня сессия через неделю, а задачи так и не решены. Как застрял на первой, так и все. На бумаге то вроде решил, а вот...
C++ После всякиго ли компилятора c++ будут гарантирвоано правильно исполняться код, соответствющий этим строкам? После всякиго ли компилятора c++ будут гарантирвоано правильно исполняться код, соответствующий этим: a=a; // Удвоение a*=a; // Возведение в квадрат строкам. 1. На сколько надёжно компилится каждая из этих строк, если a - число встроенного типа: 1.1. влезающее в аппаратный регистр, 1.2. разрядностью больше регистра (если такое поддерживается встроенными типами)? 2. Как гарантировать... подробнее

Показать сообщение отдельно
ValeryLaptev
Эксперт С++
1035 / 814 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
25.05.2011, 23:24     realloc и вызов конструктора
Вот дополнительный материал по управлению памятью. Дам в нескольких ответах - много материала.

Свободная память и куча
Динамическая память выделяется объектам во время выполнения программы с помощью специальных операций 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, а остальные — обнуляет.
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru