Оператор New и создание массива экземпляров класса
Как с помощью оператора new создать массив классов с параметрами в конструкторе
т.е. есть клас cl_1, и его конструктор cl_1(int), и есть указатель на начало массива: cl_1 *mas;
тогда например создание одного экземпляра класса: mas = new cl_1(A);, где А соответственнон - параметр для конструктора. А как мне создать и инициализировать таким образом массив из N экземпляров класса???
ну это-то не проблема... эх.
ArrayList - это билдерская штука? не люблю их использовать. Лучше когда все тобою писано )
using namespace std;
...
class Test
{
public:
Test(){}
Test(int a):myData(a){}
private:
int myData;
};
vector<Test>* mas; //Объявляем указатель на динамический массив объектов Test в динамической памяти;
mas= new vector <Test> (10, 25); //Инициализируем динамический массив из 10 объектов Test с передаваемым параметром "25".
mas->push_back(Test(44)); //Добавляем в конец массива 11й элемент типа Test инициализированный значением 44
(*mas)[10]=21; //Обращаемся к элементу массива по индексу, изменяем значение 11го элемента на 21... Применяем скобки() для установки приоретета оператора разыменования.
mas->pop_back();//Удаляем последний(11й) элемент массива
// ... какой то код
delete mas; //Освобождаем выделенную память
Vector - это часть C++. Конечно несколько навороченней чем стандартный статический массив, но и удобней.
Я просто думал, что у оператора new со всеми его перегрузками безграничные возможности.
В остальном с вектором можно работать как с обычным массивом через индексы...
(*mas)[10]=21;
delete mas;
Плюс получаете гибкость с добавлением новых элементов в любой момент выполнения программы.
Я просто думал, что у оператора new со всеми его перегрузками безграничные возможности.
Ага, может тебе еще проще будет вместо деструктора написать процедуру?? Это называется извращаться через ж***. Конструкторы для того и предусмотрены - чтобы создать и инициализировать элементы обьекта, и ничего в них сложного нет.
2Гигахард: спасибо, думаю остановлюсь всеже на этом способе.
ArrayList - это билдерская штука?
Нет, это дотнетовская.
не люблю их использовать. Лучше когда все тобою писано )
И очень зря. Сначала, конечно, нужно разобраться, что такое массив, что такое динамический массив и.т.д. Но затем практически всегда гораздо лучше использовать готовые, выверенные, мощные и отлично документированные решения, чем изобретать велосипед. Кодя то, что давно уже было кем то сделано до тебя, ты просто теряешь время, которое мог бы употребить на проработку логики, например.
[QUOTE=Gigahard]
Vector - это часть C++. Конечно несколько навороченней чем стандартный статический массив, но и удобней.[/QUOTE]
Звучит так, словно ты извиняешься за навороченность вектора. Нечто вроде "Не слишком хорошее решение, но все же получше чем твое". Почему?
ой как все сложно =() мне тогда проще будет добавить функцию Init с параметрами для инициализации и вызывать в цикле.
Надо просто один раз не пожалеть времени и сил и досконально разобраться. Или ты предпочитаешь потом тратить в разы больше сил, раз за разом создавая кривоватый код?:)
Я просто думал, что у оператора new со всеми его перегрузками безграничные возможности.
Возможностей много. Но вовсе не безграничны они. И кстати, тот самый вектор использует в своей внутренней реализации этот оператор для выделения памяти под элементы. Только незаметно для пользователя вектора.
[QUOTE=Gigahard]
Да ничего сложного... Комментов больше, чем программного кода. Одно "лишнее" ключевое слово и два вида скобочек...
В остальном с вектором можно работать как с обычным массивом через индексы...
Плюс получаете гибкость с добавлением новых элементов в любой момент выполнения программы.[/QUOTE]
Без обид, но ощущение такое, как будто ты ловкий торговец и пытаешься всучить вектор автору темы, всячески рекламируя его полезность:D.
mas= new vector <Test> (10, 25); //Инициализируем динамический массив из 10 объектов Test с передаваемым параметром "25".
У вектора нет конструктора, который в качестве параметра берет значение, не являющееся объектом того класса, какого есть вектор, т. е. если имеется vector<Test>, мы в конструктор вектора первым параметром передаем число элементов, вторым - объект типа Test.
При этом сам вектор, создавая объекты Test, вызывает их копирующий конструктор.
Приведенный вами код компилируется, более того, работает правильно в MS VC 2005, хотя не должен. Не буду рассказывать о результатах отладки этого кода, но похоже, тут глюк компилятора.
В приведенном коде по логике вещей должен вызываться конструктор
template<class _Iter>
vector(_Iter _First, _Iter _Last),
т. е. 10 и 25 должны рассматриваться как итераторы.
Выхода из ситуации два:
1. vector <Test> (10, Test(25))
2. Используем вызов оператора new таким образом, чтобы не вызывались конструкторы объектов. Потом отдельно для каждого объекта вызываем конструктор. Тут возможна передача произвольного числа произвольных параметров:
{
public:
Test(int a, double b):myData(a), data2(b) {};
private:
int myData;
double data2;
};
Test *arr = reinterpret_cast<Test*> (::operator new[] (sizeof (Test) * 10));
Test *ptr = arr;
for (size_t i = 0; i < 10; ++i, ++ptr)
::new (ptr) Test (12, 55.77);
3. Если все-таки нужен вектор, то вот так:
vec.reserve(10);
for (size_t i = 0; i < 10; ++i)
vec.push_back(Test(12, 55.77));
Но здесь происходит инициализация локального объекта Test, затем его копирование в вектор, т. е. лишние затраты времени на копирование. Более того, если объект имеет семантику Noncopyable, такой код не проканает.
В C++ запись Test x=25 равнозначна записи Test x= Test(25).
Для этого в классе должен быть определен перегруженный конструктор, который принимает один параметр присваиваемого типа.
При этом происходит неявная инициализация передаваемого значения в объект Test. Данное правило справедливо только для конструкторов с одним передаваемым параметром.
Т.е. запись vector <Test> (10,25) равнозначна коду vector <Test> (10, Test(25)).
Оба варианта равноправны и не являются ошибочными.
В C++ запись Test x=25 равнозначна записи Test x= Test(25).
Для этого в классе должен быть определен перегруженный конструктор, который принимает один параметр присваиваемого типа.
При этом происходит неявная инициализация передаваемого значения в объект Test. Данное правило справедливо только для конструкторов с одним передаваемым параметром.
Т.е. запись vector <Test> (10,25) равнозначна коду vector <Test> (10, Test(25)).
Оба варианта равноправны и не являются ошибочными.
Однако, отладка под MS VC++ 2005 показала, что это не так. Для кода
vector<Test> vec (10, 25)
вызывается конструктор (как я писал выше)
template<class Iter>
vector(Iter first, Iter last)
Но каким-то чудом этот конструктор приводит к правильной работе :)
А записи
Test x = 25
и
Test x = Test(25)
неравнозначны.
Во-первых, Test x = 25 равнозначна записи Test x (25) и требует наличия конструктора с параметром типа int или иного целого типа. Он и вызывается.
Во-вторых, запись Test x = Test (25) равнозначна записи
Test x (Test(25))
и здесь нужны два конструктора - описанный выше и копирующий конструктор. Сработают оба. Создается временный безымянный объект Test (25), который передается параметром в копи-конструктор и тут же уничтожается.
А в записи
vector<Test> vec (10, 25)
второй параметр в любом случае не воспринимается компилятором как значение типа Test, тут нужно явное преобразование, о котором я писал:
vector<Test> vec (10, Test(25)).
Это, конечно, небольшой оффтоп, но надо проверить на других компиляторах, чтобы выяснить, кто прав :)
Сразу - нет, не получится, массивы инициализируются только конструктором по умолчанию.
"Нормальный", рабочий код приведен выше.
К тому же подобный код используется (как правило, но не обязательно всегда) классом vector. Т. е. сначала выделяется память под нужное число элементов, потом каждый отдельно инициализируется.
Примерно такова же и внутренняя реализация оператора new[] (опять же не обязательно всегда), который выделяет массивы.
Так что по затратам времени приведенный код ничем не уступает ни вектору (где вызывается копи-конструктор с одним параметром), ни объявлению массива (где вызывается конструктор по умолчанию), а имеет только преимущество - произвольное число произвольных параметров. Так что Ctrl+C - Ctrl+V и пошел :)
" Test(int a, double b):myData(a), data2(b) {};" что означает все что после двоеточия? (честно говоря остальные несколько строчек тоже не совсем понятны) В общем - спасибо, буду разбираться.
" Test(int a, double b):myData(a), data2(b) {};" что означает все что после двоеточия? (честно говоря остальные несколько строчек тоже не совсем понятны) В общем - спасибо, буду разбираться.
Поясняю правила инициализации подобъектов в конструкторе.
После двоеточия идет инициализация базовых классов и подобъектов, т. е. членов класса. В данном случае имеется два подобъекта: int myData и
double data2. Они инициализируются значениями, которые переданы конструктору в параметрах (т. е. a и b).
Если бы класс был унаследован от другого, то сначала идет инициализация базовых классов, затем уж подобъектов. Например
{
public:
Base (int _x)
:x (_x)
{
};
private:
int x;
};
class Derived : public Base // Наследуем этот класс от Base
{
public:
Derived (int _x, double _y)
:Base (_x), y (_y)
{
};
private:
double y;
};
Не буду вдаваться в подробности, но инициализацию подобъектов очень желательно производить именно таким образом, а не таким, например:
{
public:
Derived (int _x, double _y)
:Base (_x)
{
y = _y;
};
private:
double y;
};
Более сложный код:
Test *ptr = arr;
for (size_t i = 0; i < 10; ++i, ++ptr)
::new (ptr) Test (12, 55.77);
Поясняю.
Имеется оператор new (и new[] для массивов).
Если вызывать его в обычной форме, как например
Test *arr = new Test [10];
компилятор генерирует код, который вызывает непосредственно сам оператор new (который занимается выделением памяти), плюс код для вызова конструкторов.
А вот если написать
operator new[],
то вызывается только непосредственно оператор new, без вызова конструкторов (т. е. происходит чисто выделение памяти).
Идем дальше.
Есть специальная форма оператора new, называемая placement operator new, которая просто ничего не делает. Эта форма рассчитана на то, что память уже выделена. Этот оператор в качестве параметра принимает указатель на память, которая была уже выделена.
Тогда код
new (Указатель) Тип (параметры)
будет компилироваться так. Как я и указывал выше, будет сгенерирован код, вызывающий непосредственно оператор (который ничего не делает), затем - вызов конструктора. По сути, такая форма оператора new - единственный способ "явно" вызвать конструктор объекта.
Итак:
Test *ptr = arr; // ptr - указатель на текущий элемент массива (двигается в цикле)
for (size_t i = 0; i < 10; ++i, ++ptr)
::new (ptr) Test (12, 55.77); // Вызов конструктора для очередного элемента
Не буду вдаваться в подробности, но инициализацию подобъектов очень желательно производить именно таким образом
Ну а если вдаваться в подробности? :)
Более сложный код:
Test *ptr = arr;
for (size_t i = 0; i < 10; ++i, ++ptr)
::new (ptr) Test (12, 55.77);
Да, действительно сложный.
Почему бы не сделать так?
for (size_t i = 0; i < 10; ++i)
::new (&arr) Test (12, 55.77);
Ну а если вдаваться в подробности? :)
Подробно: для подобъекта вызывается конструктор по умолчанию, а затем оператор присваивания (для вышеприведенного примера). Что зачастую излишне.
А если конструктора по умолчанию нет? А если оператора присваивания нет?
Почему бы не сделать так?
А new unsigned char [...] ... не вызовет ли конструирование массива объектов типа char значениями по умолчанию? Что опять же излишне? Тем более, если речь идет не о встроенных типах?
Почему бы не сделать так?
::new (&arr) Test (12, 55.77);
Пардон, привычка, появившаяся недавно.
В данный момент времени речь идет об обычном массиве, и приведенный Вами код вполне хорош.
Но если речь идет о таких вещах, как vector, может получиться так, что для каждой итерации цикла вызывается оператор [], что может занять большее время, чем ++ptr. В то же время мы знаем, что данные в vector располагаются "рядом", и ++ptr приведет нас гарантированно к следующему элементу вектора, чем мы и воспользовались.
vector<Test> vec (10, 25)
вызывается конструктор (как я писал выше)
template<class Iter>
vector(Iter first, Iter last)
Но каким-то чудом этот конструктор приводит к правильной работе :)
А записи
Test x = 25
и
Test x = Test(25)
неравнозначны.
Во-первых, Test x = 25 равнозначна записи Test x (25) и требует наличия конструктора с параметром типа int или иного целого типа. Он и вызывается.
Во-вторых, запись Test x = Test (25) равнозначна записи
Test x (Test(25))
и здесь нужны два конструктора - описанный выше и копирующий конструктор. Сработают оба. Создается временный безымянный объект Test (25), который передается параметром в копи-конструктор и тут же уничтожается.
А в записи
vector<Test> vec (10, 25)
второй параметр в любом случае не воспринимается компилятором как значение типа Test, тут нужно явное преобразование, о котором я писал:
vector<Test> vec (10, Test(25)).
Это, конечно, небольшой оффтоп, но надо проверить на других компиляторах, чтобы выяснить, кто прав :)
Читаем справочник Шилдта по С++, описывающий ANSI стандарт, Раздел "Конструкторы с параметрами:особый случай"
Запись Test x=25 является эквивалентом записи Test x= Test(25) и для компилятора они равнозначны!
Под билдером шестым тоже никаких глюков не замечено.
ИМХО в случае с классами, вектор не занимается проверкой типов, возлагая эту ответственность на конструкторы самого класса, а они в свою очередь допускают такой формат записи.
Да, действительно, признаю ошибку.
Но все-таки я прав насчет вызова конструктора, поскольку первый конструктор больше подходит, т. к. в нем нет неявного преобразования типов.
А вот запись
vector<Test> vec (10, Test(25))
гарантированно даст нужный результат.
А если конструктора по умолчанию нет? А если оператора присваивания нет?
Правильный ответ.
А new unsigned char [...] ... не вызовет ли конструирование массива объектов типа char значениями по умолчанию? Что опять же излишне?
Не вызовет.
Тем более, если речь идет не о встроенных типах?
Речь идет о конкретном типе unsigned char[], ну можно и char[]. И используется он для выделения памяти. Ты видимо не понял примера.
Пардон, привычка, появившаяся недавно.
В данный момент времени речь идет об обычном массиве, и приведенный Вами код вполне хорош.
Но если речь идет о таких вещах, как vector, может получиться так, что для каждой итерации цикла вызывается оператор [], что может занять большее время, чем ++ptr. В то же время мы знаем, что данные в vector располагаются "рядом", и ++ptr приведет нас гарантированно к следующему элементу вектора, чем мы и воспользовались.
Ещё раз. Не разрывай пример. Речь идет о типе unsigned char[]. Здесь vector ни с какого боку.
P.S. Кстати, про скорость... ты имеешь неверное представление об оптимальности. Скорость работы не должна быть помехой в разрабртке, а преждевременная оптимизация опасна.