Являются ли ссылки, выделенные в куче, ужасной идеей? Зачем? - PullRequest
7 голосов
/ 24 августа 2011

Чтобы лучше объяснить эту проблему, я построил простой пример. Скажем, у меня есть класс Blob следующим образом:

class Blob
{
    string personalName;
    string& familyName;
}

A Blob может быть порожден Создателем (он же Программист), после чего он может выбрать personalName и, поскольку он имеет привилегию быть 1-м поколением Blob, он может выбрать его собственный familyName.

В качестве альтернативы, Blob может быть создан путем порождения существующего Blob, после чего он выбирает свой собственный personalName, но делит familyName со всеми другими Blob, которые были клонированы в эта семья. Если один Blob меняет фамилию, все остальные члены семьи получают это имя автоматически.

Пока все звучит хорошо, пока при написании конструктора Blob я не вижу это:

Blob::Blob() :
    personalName(pickName()),
    familyName(pickFamilyName())
{ }

...

string& Blob::pickFamilyName()
{
    return *(new string("George"));
}   // All Blobs have family name "George" in this example

Ик! Выделение памяти в куче, а затем присвоение ей ссылочной переменной ?! Это выглядит страшно!

Правильны ли мои инстинкты, что с этим что-то очень не так, или мне это кажется странным только потому, что это не обычная модель? Если что-то не так, что это? Почему это плохой дизайн?

Примечание: было бы важно освободить эту выделенную кучу память путем подсчета ссылок и удаления памяти, когда последний BLOB-объект уничтожен, или каким-либо другим способом.

Ответы [ 3 ]

14 голосов
/ 24 августа 2011

Единственный раз, когда имеет смысл хранить ссылку в качестве члена класса, это когда:

  1. Данные не принадлежат классу и класс не отвечает за его освобождение;
  2. Время жизни указанного объекта гарантированно будет больше, чем время жизни содержащего объекта.

Ваш пример нарушает правило 1.

8 голосов
/ 24 августа 2011

Я думаю, что "вонючая" ​​часть хранит переменную как ссылку на строку, так как может быть трудно отследить, является ли это допустимым объектом.Почему бы не использовать что-то вроде:

boost::shared_ptr<std::string> Blob::pickFamilyName()
{
    return boost::shared_ptr<std::string>(new std::string("George"));
}

РЕДАКТИРОВАТЬ

Согласно совету Преториана, вы можете избежать ручного выделения памяти самостоятельно:

boost::shared_ptr<std::string> Blob::pickFamilyName()
{
    return boost::make_shared<std::string>("George");
}
1 голос
/ 24 августа 2011

Одним аргументом будет согласованность: operator new возвращает указатель, а operator delete принимает указатель, поэтому ожидается, что тип, используемый для ссылки на динамически размещенные объекты, также будет указателем, а не ссылкой.Это серьезный аргумент: если вы непоследовательны и идете против программистских привычек без уважительной причины, вы путаете их и выявляете ошибки.Никто не ожидал бы, что функция, которая возвращает ссылку для создания новых объектов в куче, которую затем должен удалить вызывающий код, так что рано или поздно кто-нибудь забудет это сделать.

Но есть и прагматичные причины, по которымуказатели могут быть переназначены, и то, что они могут быть установлены в нуль, делает их более удобными для обработки динамически размещаемых объектов.В вашем примере класс Blob отвечает за вызов delete для ссылочного члена.Вы обычно делаете это в деструкторе.Но представьте, что вы хотите быстрее освободить память: с помощью указателей вы можете назначить им значение null после вызова delete, а затем позволить их деструктору безопасно снова вызвать delete, со ссылочными элементами у вас останется свисающая ссылка, которую вы не можете сделатьчто-нибудь о.

Еще более серьезной проблемой является безопасность исключений: если бы у Blob был более длинный список инициализаторов или непустое тело, конструктор мог бы бросить после вызова метода pickFamilyName ().В этом случае деструктор не вызывается, и у вас есть утечка памяти.В идеале вы должны использовать RAII для этого, но с помощью указателей также можно назначить указатель на null в списке инициализатора, а затем указать его на вновь созданный объект в теле конструктора в блоке try / catch, который обеспечит объектудаляется, даже если конструктор выбрасывает и нет вызова деструктора.Это снова не может быть сделано со ссылками.

...