#c
#c
Вопрос:
У меня, как у разработчика Java, возник следующий вопрос по C .
Если у меня есть объекты типа A, и я хочу сохранить их коллекцию в массиве, то должен ли я просто хранить указатели на объекты или лучше хранить сам объект?
На мой взгляд, лучше хранить указатели, потому что: 1) Можно легко удалить объект, установив для его указателя значение null 2) Экономится место.
Комментарии:
1. зависит от использования и контейнера..
2. Я думаю, что под «ссылками» вы имеете в виду указатели. Ссылкам в C нельзя присвоить значение null, и вы в любом случае не можете определить массив ссылок.
3. контейнер — это массив, и объекты могут добавляться и удаляться во время выполнения
4. @Nerdtron: Однако вам все равно нужно было бы каким-то образом управлять временем жизни объекта, и общее использование памяти было бы больше (сами объекты плюс указатели в массиве).
5. При работе с динамическими массивами хранение указателей вместо значений «приводит к потере» указателя для каждого элемента массива, но «сохраняет» значение для каждого элемента в объеме массива. Таким образом, для больших объектов, где вы не можете надежно хранить
capacity()
иsize()
закрывать массивы указателей, все еще может быть экономия места.
Ответ №1:
Указатели или просто объекты?
В C нельзя помещать ссылки в массив. Вы можете создать массив указателей, но я все равно предпочел бы контейнер и фактические объекты, а не указатели, потому что:
- Нет возможности утечки, с безопасностью исключений легче справиться.
- Это не значит, что места меньше — если вы храните массив указателей, вам нужна память для объекта плюс память для указателя.
Единственный случай, когда я бы рекомендовал помещать указатели (или, лучше, интеллектуальные указатели) в контейнер (или массив, если необходимо), — это когда ваш объект не поддается интерпретации при копировании и присваиванию (требование для контейнеров, указатели всегда соответствуют этому) или вам нужно, чтобы они были полиморфными. Например.
#include <vector>
struct foo {
virtual void it() {}
};
struct bar : public foo {
int a;
virtual void it() {}
};
int main() {
std::vector<foo> v;
v.push_back(bar()); // not doing what you expected! (the temporary bar gets "made into" a foo before storing as a foo and your vector doesn't get a bar added)
std::vector<foo*> v2;
v2.push_back(new bar()); // Fine
}
Если вы хотите пойти по этому пути, могут быть интересны контейнеры с увеличенными указателями, потому что они выполняют всю тяжелую работу за вас.
Удаление из массивов или контейнеров.
Присвоение NULL
не приводит к уменьшению количества указателей в вашем контейнере / массиве ( delete
он также не обрабатывает), размер остается тем же, но теперь есть указатели, которые вы не можете юридически разыменовывать. Это делает остальную часть вашего кода более сложной в виде дополнительных инструкций if и запрещает такие вещи, как:
// need to go out of our way to make sure there's no NULL here
std::for_each(v2.begin(),v2.end(), std::mem_fun(amp;foo::it));
Мне действительно не нравится идея разрешать NULL
s в последовательностях указателей в целом, потому что вы быстро заканчиваете тем, что хороните всю реальную работу в последовательности условных операторов. Альтернативой является то, что std::vector
предоставляет erase
метод, который использует итератор, чтобы вы могли писать:
v2.erase(v2.begin());
чтобы удалить первое или v2.begin() 1
для второго. Хотя в std::vector
нет простого метода «стереть n-й элемент» из-за трудоемкости — если вы выполняете много операций стирания, то есть другие контейнеры, которые могут быть более подходящими.
Для массива вы можете имитировать стирание с помощью:
#include <utility>
#include <iterator>
#include <algorithm>
#include <iostream>
int main() {
int arr[] = {1,2,3,4};
int len = sizeof(arr)/sizeof(*arr);
std::copy(arr, arr len, std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
// remove 2nd element, without preserving order:
std::swap(arr[1], arr[len-1]);
len -= 1;
std::copy(arr, arr len, std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
// and again, first element:
std::swap(arr[0], arr[len-1]);
len -= 1;
std::copy(arr, arr len, std::ostream_iterator<int>(std::cout, " "));
std::cout << std::endl;
}
для сохранения порядка требуется серия перетасовок вместо одной замены, что прекрасно иллюстрирует сложность стирания этих std::vector
граней. Конечно, делая это, вы только что заново изобрели довольно большое колесо, намного менее полезное и гибкое, чем стандартный библиотечный контейнер, который можно использовать бесплатно!
Комментарии:
1. Также «удаление» объекта путем присвоения указателю значения null неестественно и добавляет ненужную сложность коду.
2. 1) перемещение реальных объектов может быть дорогостоящим (например, если вы вставляете в середину вектора) 2) вы не можете создать полиморфный контейнер с реальными объектами.
3. @kbok the но как бы вы удалили объект из массива?
4. @yi_h — хорошее замечание о конструкторе копирования и полиморфных вещах — я добавил упоминание о требованиях к копированию, которые можно создавать и назначать для контейнеров, и пример того, где полиморфность имеет значение.
5. @user695652 Если вы хотите удалить объекты из контейнера, то массив — это не то, что вам нужно.
Ответ №2:
Похоже, вы путаете ссылки с указателями. C имеет 3 распространенных способа представления дескрипторов объектов
- Ссылки
- Указатели
- Значения
Исходя из Java, наиболее аналогичный способ — сделать это с помощью указателя. Вероятно, это то, что вы пытаетесь здесь сделать.
Однако то, как они хранятся, оказывает довольно фундаментальное влияние на их поведение. При сохранении в виде значения вы часто имеете дело с копиями значений. Где указатели имеют дело с одним объектом с несколькими ссылками. Дать однозначный ответ, что одно лучше другого, на самом деле невозможно без немного большего контекста того, что делают эти объекты
Ответ №3:
Это полностью зависит от того, что вы хотите сделать … но в некотором смысле вы заблуждаетесь.
Вам следует знать следующие вещи:
- Вы не можете присвоить ссылке значение NULL в C , хотя вы можете присвоить указателю значение NULL.
- Ссылка может быть сделана только на существующий объект — он должен инициализироваться как таковой.
- Ссылка не может быть изменена (хотя значение, на которое ссылается ссылка, может быть).
- Вы бы не экономили место, на самом деле вы бы использовали больше, поскольку вы используете объект и ссылку. Если вам нужно ссылаться на один и тот же объект несколько раз, вы экономите место, но с тем же успехом вы можете использовать указатель — он более гибкий в БОЛЬШИНСТВЕ (читай: не во всех) сценариев.
- Последнее важное замечание: контейнеры STL (vector, list и т.д.) Имеют семантику КОПИРОВАНИЯ — они не могут работать со ссылками. Они могут работать с указателями, но это усложняется, поэтому пока вы всегда должны использовать копируемые объекты в этих контейнерах и соглашаться с тем, что они будут скопированы, нравится вам это или нет. STL разработан так, чтобы быть эффективным и безопасным с семантикой копирования.
Надеюсь, это поможет! 🙂
PS (РЕДАКТИРОВАТЬ): Вы можете использовать некоторые новые функции в BOOST / TR1 (погуглите их) и создать контейнер / массив shared_ptr (интеллектуальные указатели для подсчета ссылок), который даст вам ощущение, аналогичное ссылкам Java и сборке мусора. Существует множество различий, но вам придется прочитать об этом самостоятельно — они являются отличной особенностью нового стандарта.
Комментарии:
1. «должен ли я просто хранить указатели на объекты или лучше хранить сам объект?»
2. В его первоначальном вопросе никогда не упоминался указатель, в нем везде упоминались ссылки, пытающиеся обрабатывать их так, как они использовались бы в Java, что невозможно. 😉
Ответ №4:
Вы всегда должны хранить объекты, когда это возможно; таким образом, контейнер будет управлять временем жизни объектов за вас.
Иногда вам потребуется хранить указатели; чаще всего, указатели на базовый класс, где сами объекты будут разных типов. В этом случае вам нужно быть осторожным, чтобы самостоятельно управлять временем жизни объектов; гарантируя, что они не будут уничтожены во время нахождения в контейнере, но что они будут уничтожены, как только в них больше не будет необходимости.
В отличие от Java, присвоение указателю значения null не освобождает объект, на который указано; вместо этого вы получаете утечку памяти, если на объект больше нет указателей. Если объект был создан с использованием new
, то delete
в какой-то момент он должен быть вызван. Наилучшими вариантами здесь являются хранение интеллектуальных указателей ( shared_ptr
или, возможно unique_ptr
, если таковые имеются) или использование контейнеров указателей Boost.
Ответ №5:
Вы не можете хранить ссылки в контейнере. Вместо этого вы могли бы хранить (голые) указатели, но это чревато ошибками и поэтому вызывает неодобрение.
Таким образом, реальный выбор заключается между хранением объектов и интеллектуальными указателями на объекты. И то, и другое имеет свое применение. Моей рекомендацией было бы использовать сохранение объектов по значению, если конкретная ситуация не требует иного. Это может произойти:
- если вам нужно обнулить объект, не удаляя его из контейнера;
- если вам нужно хранить указатели на один и тот же объект в нескольких контейнерах;
- если вам нужно обрабатывать элементы контейнера полиморфно.
Одна из причин, по которой не следует этого делать, заключается в экономии места, поскольку хранение элементов по значению, вероятно, будет более экономичным.
Ответ №6:
Чтобы добавить к ответу aix:
Если вы хотите хранить полиморфные объекты, вы должны использовать интеллектуальные указатели, потому что контейнеры создают копию, а для производных типов копируют только базовую часть (по крайней мере, стандартные, я думаю, у boost есть несколько контейнеров, которые работают по-другому). Таким образом, вы потеряете любое полиморфное поведение (и любое состояние производного класса) ваших объектов.