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

C++

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 153, средняя оценка - 4.65
pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
#1

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

20.05.2011, 13:52. Просмотров 18630. Ответов 35
Метки нет (Все метки)

здраствуйте! мне препод сказал, что можно выделить память оператором new, а потом довыделить её с помощью realloc и каким-то образом вызвать конструкторы для новой памяти(ну или это я так его понял). Как это можно сделать?

задание вобще такое:
Во всех вариантах необходимо первоначально создать шаблон класса для работы с массивом произвольного типа данных. Шаблон должен включать:
указатель, хранящий адрес размещения массива в динамической памяти;
целочисленную переменную, показывающую количество занятых элементов массива;
конструктор без параметров, создающий динамический массив заданного типа, с нулевым количеством занятых элементов;
конструктор копирования;
очистку массива;
метод «обработки массива»;
деструктор.
Далее на основе данного шаблона создать класс для работы со строкой символов, специализировав метод «обработки массива» для вашей конкретной задачи.
я хотел класс allocator использовать, но нельзя((. Пока на примете только один вариант - выделять с помощью new и как только память заполниться выделять участок в два раза крупнее копировать старые данные и подчищать за ними. Есть ещё варианты?
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
20.05.2011, 13:52     realloc и вызов конструктора
Посмотрите здесь:

вызов конструктора - C++
Здарова! Есть допустим класс: class Str { Str(); Str(Str&); Str(char*); };

вызов конструктора - C++
Почему при повторном вызове конструктора выбивает ошибку ?Вот здесь ObjX(INUSE); no match for call to `(aClass) (int&)' #include...

Вызов конструктора - C++
void main() { std::string stemporary; int itemporary; float ftemporary; float fftemporary; ECM *pECM; for (int i=0;i<4;i++) ...

Вызов базового конструктора - C++
Такая вот ситуация. Думаю, проблема в объявлении и инициализации массива m. Я бы инициализировал уже в конструкторе, но сначала вызовет...

Вызов конструктора классf! - C++
Всем привет! Вот есть конструктор. SampleTable::SampleTable(const sp<DataSource> &source) : mDataSource(source), ...

Неоднозначный вызов конструктора - C++
class Verylong{ public: Verylong (long x = 0) { enter(x);} Verylong (const Verylong& vrl) : _sign(vrl._sign),...

Вызов конструктора с аргументами - C++
есть класс приложения test_proj в интерфейсе класса в файле test_proj.h создается объект класса Settings. class test_proj { ...

После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
20.05.2011, 16:34  [ТС]     realloc и вызов конструктора #16
ну я понял, просто я думал есть какие-то более рациональные варианты. Пока вы охотник с Evg обсуждали тут... Я накатал следующее:

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
90
91
92
template <typename T> class vector
    {
        typedef unsigned short int uint;
    private:
        T*          arr;
        uint        _capacity,
                    _size;
 
        bool        resize();
 
    public:
        vector();
        vector(const vector<T> &Q);
 
        T operator[](const uint &index);
 
        void        push_back(const T &value);
        
        uint        size();
        bool        empty();
 
 
        ~vector();
    };
 
    template <typename T> vector<T>::vector() : _size(0), _capacity(0)
    {
        arr = NULL;
    }
 
    template <typename T> vector<T>::~vector() 
    {
        delete[] arr;
    }
 
    template <typename T> vector<T>::vector(const vector<T> &Q) 
    {
        this->_size = Q._size;
        this->_capacity = Q._capacity;
 
        arr = new T[_capacity];
        for (uint i = 0; i != _size; i++) {
            arr[i] = Q.arr[i];
        }
 
 
    }
 
    template <typename T> void vector<T>::push_back(const T &value) 
    {
        if (  empty()  ) 
        {
            arr = new T(value);
            _capacity = _size = 1;
 
        }
        else 
        {
            if (  _size == _capacity  ) 
            {
                resize();   
            }
            arr[_size] = value;
            _size++;
        }
 
    }
 
    template <typename T> T vector<T>::operator[](const uint &index)
    {
        return arr[index];
    }
 
    template <typename T> bool vector<T>::resize() 
    {
        T* NewArea = new T[_capacity * 2];
        for (uint i = 0; i != this->_size; i++) {
            NewArea[i] = this->operator[](i);
        }
 
        _size == 1 ? delete arr : delete[] arr;
 
        arr         =    NewArea;
        _capacity   *=   2;
 
        return true;
        
    }
 
    template <typename T> bool vector<T>::empty() {
        return (_size == 0);
    }
когда size подходит к capacity я выделяю вдвоё больший кусок. Так примерно должно быть?
Evg
Эксперт CАвтор FAQ
17542 / 5780 / 370
Регистрация: 30.03.2009
Сообщений: 15,920
Записей в блоге: 26
20.05.2011, 16:41     realloc и вызов конструктора #17
Сообщение было отмечено автором темы, экспертом или модератором как ответ
Цитата Сообщение от pito211 Посмотреть сообщение
когда size подходит к capacity я выделяю вдвоё больший кусок.
Это не есть хорошо, потому что чем больше памяти будешь выделять, тем больше будет вероятность того, что память жрётся неэффективно (да ещё и экспотенциально). Обычно делают так, что выделяют некий квант (например, 1024 элемента) и выделяют память квантами. Т.е. изначально 1024 элемента, после того, как исчерпали - увеличиваем на 1024 элемента.

Ещё более грамотным будет плавающее значение кванта в зависимости от размера структуры. Т.е.определить квант, например, в (4096 / sizoef элемента). В этом случае независимо от размера элемента у тебя память будет расходоваться кусками по 4096 байт (что составляет одну страницу памяти - система работает с такими же квантами). Но тут будут ньюансы: если размер элемента больше 4096 или когда 4096 нацело не делится. Тут надо более аккуратно работать, а потому такой вариант годится скорее для саморазвития, чем для сдачи задания (потому что чем сложнее реализация, тем больше можно наделать ошибок)
ForEveR
В астрале
Эксперт С++
7970 / 4732 / 320
Регистрация: 24.06.2010
Сообщений: 10,541
Завершенные тесты: 3
20.05.2011, 16:57     realloc и вызов конструктора #18
Evg, Ну в STL множитель где-то 1.5 как пишет Саттер. Да и в MSVS реализации вектора было именно так.
pito211
186 / 173 / 8
Регистрация: 22.03.2010
Сообщений: 612
20.05.2011, 17:07  [ТС]     realloc и вызов конструктора #19
а лафоре хвалит коэффицент х2 в главе про класс алокатор

говорит удивительнорго эффективный
Evg
Эксперт CАвтор FAQ
17542 / 5780 / 370
Регистрация: 30.03.2009
Сообщений: 15,920
Записей в блоге: 26
20.05.2011, 17:23     realloc и вызов конструктора #20
Цитата Сообщение от ForEveR Посмотреть сообщение
Evg, Ну в STL множитель где-то 1.5 как пишет Саттер
Цитата Сообщение от 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
Форумчанин
Эксперт С++
4514 / 2856 / 228
Регистрация: 12.12.2009
Сообщений: 7,249
Записей в блоге: 1
Завершенные тесты: 1
21.05.2011, 10:00     realloc и вызов конструктора #22
pito211, если интересно, скачай "Философия С++" 1-ый том. Стр.414-422. Там очень подробно расписанно про то, что ты нашел.
ForEveR
В астрале
Эксперт С++
7970 / 4732 / 320
Регистрация: 24.06.2010
Сообщений: 10,541
Завершенные тесты: 3
21.05.2011, 10:56     realloc и вызов конструктора #23
pito211, placement new - хорошая вещь, да. Но используется редко все же.
Evg
Эксперт CАвтор FAQ
17542 / 5780 / 370
Регистрация: 30.03.2009
Сообщений: 15,920
Записей в блоге: 26
21.05.2011, 11:00     realloc и вызов конструктора #24
pito211, placement new в твоём случае принципиально не отличается. Ибо что вызов new, что вызов malloc + new дадут по сути один и тот же результат. При этом на пару с realloc этот placement new всё равно работать толком не будет. В том смысле, что ничего нового не даст. malloc/realloc - интерфейсы для выделения plain-памяти, которая НЕ трактуется как массив объектов. Т.е. если ты вызвал realloc, то данные от объектов у тебя скопируются, но они скопируются как образ памяти, но не как объекты, потому что никаких операций присваивания или конструкторов вызываться не будет (хотя новые объекты как бы появились). Такая работа может послужить дополнительным источником ошибок
ValeryLaptev
Эксперт С++
1039 / 818 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
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
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
Эксперт С++
1039 / 818 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
21.05.2011, 12:26     realloc и вызов конструктора #27
Цитата Сообщение от pito211 Посмотреть сообщение
и если это так, то это значит же что в некоторых случаях копировать не придётся, а это ведь серьёзное увеличение производительности(если вдруг не придётся тыщу элементов копировать)?
По поводу увеличения памяти - посмотрите в моем посте функцию reduce(). Вы увидите, что каждое увеличение - в 2 раза. Это не с потолка. Это как раз для того, чтобы минимизировать переписывание. Эти вопросы подробно обсуждаются Гербом Саттером в его книжках.
Evg
Эксперт CАвтор FAQ
17542 / 5780 / 370
Регистрация: 30.03.2009
Сообщений: 15,920
Записей в блоге: 26
21.05.2011, 14:22     realloc и вызов конструктора #28
Цитата Сообщение от pito211 Посмотреть сообщение
если я правильно понимаю действие реаллока, то в некоторых случаях он вернёт ссылку на новую область памяти(пустую наверно, раз консттрукторов копирования он не вызывает?), а в некоторых расширит существующую, если это возможно?
Да, если возможно, то realloc растянет существующую память. Если нужно выделить новую, то выделит новую и скопирует старые данные

Цитата Сообщение от pito211 Посмотреть сообщение
и если это так, то это значит же что в некоторых случаях копировать не придётся, а это ведь серьёзное увеличение производительности(если вдруг не придётся тыщу элементов копировать)?
Всё это хорошо для POD-типов. Не помню как расшифровывается, но это плоские типы, содержащие только данные, которые можно копировать побайтно. С классами в общем случае так поступать нельзя, потому что у класса может быть переопределён оператор присваивания. При выделении памяти по новой номинально рождаются новые объекты (а потому должен быть вызван конструктор) и уничтожаются старые объекты (при этом должен быть вызван деструктор). Realloc ничего этого не делает. Более того, после того, как отработал realloc, уже некому хотя бы смоделировать такое поведение
ValeryLaptev
Эксперт С++
1039 / 818 / 48
Регистрация: 30.04.2011
Сообщений: 1,659
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, а остальные — обнуляет.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
25.05.2011, 23:33     realloc и вызов конструктора
Еще ссылки по теме:

Неправильный вызов конструктора - C++
Доброго времени суток. Возникла такая проблема. Есть такой конструктор House(string s){ int n; string a; for (int i=1;...

Вызов конструктора копий - C++
Всех приветствую! Имеется следующий простенький код: #include &lt;iostream&gt; using namespace std; class Object { ...

Вызов конструктора класса - C++
есть класс Set, и в нем есть конструктор, как с этого конструктора мне массивы перенести в метод другого класса так что бы над ними можно...

Повторный вызов конструктора?! - C++
Мой небольшой класс class CString { private: char* str; int len; int real_size; public: CString() : len(0),...

Вызов конструктора копии - C++
Не вызывается конструктор копии из производного класса #include &lt;iostream&gt; using namespace std; class A { int x; public: ...


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

Или воспользуйтесь поиском по форуму:
Evg
Эксперт CАвтор FAQ
17542 / 5780 / 370
Регистрация: 30.03.2009
Сообщений: 15,920
Записей в блоге: 26
25.05.2011, 23:33     realloc и вызов конструктора #30
ValeryLaptev, не проще ли ссылку на статью выложить?

 Комментарий модератора 
Ссылка удалена как нарушающая п. 3.7 правил форума.
Yandex
Объявления
25.05.2011, 23:33     realloc и вызов конструктора
Ответ Создать тему
Опции темы

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