Похоже, вы приближаетесь к С++ с управляемого языка, такого как С#. В C++ все немного иначе.
То, что вы описываете как ссылочные типы, не существует в С++. Типы в C++ не являются «ссылочными типами» и не являются «типами значений». Они просто «типы». Обрабатываются ли они с помощью ссылок (или указателей) или по значению, полностью зависит от кода, который использует тип (а не определение типа). Напротив, в таких языках, как C#, оператор объявления типа решает, должен ли тип обрабатываться как ссылка или как значение. В C++ есть что-то, что называется ссылка, но оно не имеет ничего общего с тем, что вы описываете. В конце я упомяну ссылки на C++.
Теперь давайте посмотрим, сможем ли мы обработать несколько частей вашего вопроса:
Указатель помещается в стек, а фактические данные, на которые указывает указатель, создаются и помещаются в кучу.
Может быть. Это было бы верно, если бы вы создали такой объект, например:
class MyClass { /* ... */ };
...
MyClass* pObj1 = new MyClass();
MyClass* pObj2 = (MyClass*)malloc( sizeof(MyClass) );
Но нет, если вы создадите объект следующим образом:
MyClass obj3;
В последнем случае объект размещается в стеке, и указатель или ссылка не используются. Вы манипулируете им как «типом значения».
MyClass *pObj3 = &obj3;
Теперь pObj3
является указателем (в стеке) на obj3
, который также находится в стеке. Видеть? Нет связи между определением класса и местом хранения объектов этого класса. Это зависит от того, как вы используете тип. Довольно часто используется комбинация объектов одного и того же типа, основанных на стеке и куче.
Стандартные массивы и определяемые пользователем классы являются ссылочными типами. Это верно?
Нет; массивы — это просто набор объектов одного типа/размера, размещенных в последовательных ячейках памяти. Массив может быть размещен в стеке или в куче, как и в случае с отдельными объектами.
C и C++ не придают массивам какой-либо особой семантики (за одним исключением, о котором я упомяну чуть позже). После того, как они распределены, они представляют собой просто набор объектов, которые оказались последовательными. Ваша программа может использовать операции с массивами или прямые операции с указателями для доступа к отдельным членам. Это означает:
arrayOfClass[i]
точно идентично слову ((Class*)*(array + i))
. В C вы даже можете сказать i[arrayOfClass]
, и это означает то же самое, что и arrayOfClass[i]
(однако C++ будет жаловаться, потому что у него более строгие правила типов).
- Вы можете использовать операторы массива в указателях на объекты, которые не являются частью массива (и, вероятно, произойдет сбой)
- Вы можете использовать обычные операции с указателями над элементами массива.
- Вы можете выделить «большой» кусок памяти и «создать свой собственный массив», интерпретируя меньшие последовательные фрагменты этой памяти, как если бы они были членами массива меньших объектов (именно это вы получаете, когда используете malloc()).
Массивы не являются типами сами по себе; это просто удобный способ размещения нескольких объектов и более удобный в некоторых обстоятельствах способ создания указателей.
Единственным исключением из этого правила «массивы не являются специальными», которое приходит мне на ум, являются массивы case, выделенные в C++ через new
. Когда вы выделяете массив через new
, он оставляет информацию (обычно в куче, примыкающей к массиву, но это не обязательно) о том, сколько элементов содержалось в массиве, когда он был выделен. Затем вы должны использовать специальный оператор delete []
для удаления массива. delete []
находит и использует этот дополнительный бит информации для правильного удаления всех элементов массива.
Во-вторых, мой главный вопрос: всегда ли механизмы управления памятью c и c++ (malloc, free and new, delete) обрабатывают это правильно и освобождают память, на которую указывает класс или массив?
Пока вы делаете все правильно, да.
Все ли по-прежнему работает, если эти указатели каким-то образом переназначаются другим объектам того же размера/типа в куче?
Да для free()
, хотя использование указателей на другой тип при вызове free() (кроме void*
) довольно необычно. Есть законные способы использования таких вещей, но это продвинутые темы. Возможно, вы захотите, чтобы опытный разработчик рассмотрел ваш дизайн, чтобы убедиться, что это действительно уместно.
delete
это другое дело; если вы используете указатель на тип, отличный от того, что хранится в буфере во время вызова удаления, поведение будет "неопределенным" (например, вы, скорее всего, столкнетесь с ошибкой). Это потому, что delete
делает больше, чем free()
; он также вызывает метод деструктора объекта, и компилятор полагается на тип указателя для вызова надлежащего метода. Если вы используете неправильный тип указателя, будет вызван неправильный метод, и кто знает, что произойдет. Вы можете потенциально поместить что-то «еще» в буфер после того, как вы new
сделали это, но это может потребовать нетривиального объема работы и снова является сложной темой.
Также обратите внимание, что вы никогда не должны выделять с malloc()
и освобождать с delete
, а также не должны выделять с new
и освобождать с free()
. Убедитесь, что ваши методы правильно подобраны.
Что, если в классе есть член-указатель, указывающий на другой объект? Я предполагаю, что удаление/освобождение объекта класса не освобождает то, на что указывает указатель члена, это правильно?
В C++ канонический способ справиться с этим состоит в том, что класс должен иметь деструктор, и деструктор позаботится об освобождении члена-указателя. В C у вас нет выбора, и вы должны вручную очистить член указателя перед очисткой внешнего указателя.
Все это предполагает, что объект владеет содержимым, на которое указывает указатель члена. В управляемых языках, таких как C#, все объекты «принадлежат» среде выполнения и удаляются под контролем сборщика мусора, поэтому вам не нужно об этом беспокоиться. В С++. кто «владеет» объектами, на которые указывают точки-члены, определяется семантикой вашей программы, а не языком, и вы должны обратить внимание, чтобы решить, когда настало подходящее время для удаления встроенных объектов. Если вы упустите нужный момент для удаления объекта, произойдет утечка памяти; если вы удалите его слишком рано, вы получите неопределенное поведение и сбой (когда какой-то код пытается использовать объект, который уже был удален).
Итак, ссылка в C++ — это, по сути, просто указатель с небольшим количеством подсластителя, предназначенный для упрощения использования указателей определенного типа. В принципе, вы почти ничего не можете сделать со ссылками, чего нельзя сделать в C++, просто используя указатели; несколько исключений - это сложные темы, которые я пропущу (мне нужно будет найти их, чтобы отдать должное теме, и у меня нет под рукой моих ресурсов).
С вашей точки зрения ссылка на С++ — это просто указатель, который выглядит как объект стека.
CMyClass pObj1 = new CMyClass();
CMyClass& myObject = pObj1; // Create a reference to the object pointed by pObj1
pObj1->Method1(); // Use the pointer to call Method1
pObj1.Method1(); // Use the reference to call Method1
Учитывая ваш уровень знаний в C++, я мог бы пока воздержаться от ссылок, пока вы немного не поймете управление памятью в C/C++.
person
Euro Micelli
schedule
18.01.2011
C
иC++
- разные языки. Выбери один :) - person   schedule 18.01.2011new
иfree()
или смешиватьmalloc()
иdelete
в одном и том же выделенном объекте, даже для тривиальных классов.new
иdelete
не обязаны использовать одну и ту же кучу или те же библиотеки управления кучей, что иmalloc
иfree()
, поэтому смешивание двух наборов функций распределения является неопределенным поведением. Частоnew
/delete
реализуются через вызовыmalloc()
/free()
, но это не гарантируется, и легко создать пример C++, где это не так. - person Euro Micelli   schedule 18.01.2011