Дизайн класса: как вернуть shared_ptr: ссылка или копия

Вот сценарий: у меня есть класс с именем Program, который содержит три shared_ptr: для вершинного, геометрического и фрагментного шейдера. Когда создается объект Shader, он создает шейдер с помощью glCreateShader, а также компилирует его.

Денструктор Shader автоматически вызывает glDeleteShader. Итак, проблема в том, что если я сделаю следующее:

  1. Создайте объект шейдера;
  2. Скопируйте его;
  3. Уничтожить копию.

Также исходная копия становится недействительной, потому что когда копия уничтожается, она вызывает glDeleteShader. Я считаю, что это проблема дизайна.

Поэтому я избежал этой проблемы, просто используя указатели. Теперь класс Program содержит шейдеры. Я создал метод, который возвращает shared_ptr вершинным, геометрическим и фрагментным шейдерным объектам. Я сомневаюсь: должен ли я возвращать shared_ptr следующим образом:

const shared_ptr<Shader>& getVertex_shader_ptr() const
{
    return vertex_shader_ptr;
}

Или вот так:

shared_ptr<Shader> getVertex_shader_ptr() const
{
    return vertex_shader_ptr;
}

Я боюсь, что проблема, описанная выше, возникнет снова: shared_ptr освобождается, и это делает шейдер OpenGL недействительным.


person Ramy Al Zuhouri    schedule 30.01.2013    source источник
comment
Вы должны посмотреть на правило трех stackoverflow. com/questions/4172722/что такое правило трех   -  person R. Martinho Fernandes    schedule 30.01.2013


Ответы (4)


Если ваш шейдер не может иметь значение NULL, вы должны просто вернуть ссылку на него:

const Shader& getVertex_shader() const
{
    return vertex_shader;
}

Если допустимо, что ваш шейдер имеет значение NULL, но только ваша программа несет ответственность за его удаление, просто верните указатель:

const Shader* getVertex_shader_ptr() const ...

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

shared_ptr<Shader> getVertex_shader_ptr() const ...
person Bill    schedule 30.01.2013
comment
При отсутствии четкого права собственности - это не цель shared_ptr, но, к сожалению, часто кажется, что это так, когда они используются! Я всегда удостоверяюсь, что право собственности на объекты ясно, и использую общие/слабые указатели для управления/мониторинга их времени жизни. Возможно, в этом случае шейдер должен оставаться в живых до тех пор, пока его не закончат использовать, что может произойти после уничтожения владельца программы? Мы просто не знаем :) - person Ben Hymers; 30.01.2013
comment
+1 Похоже, что ОП вообще не нужен shared_ptr, все, что ему нужно, это конструктор копирования и оператор присваивания. - person Praetorian; 30.01.2013
comment
Это второй случай: шейдер может быть нулевым, а просто программа может его удалить. - person Ramy Al Zuhouri; 30.01.2013
comment
@Bill весь смысл shared_ptr подразумевает возврат по значению. Если возврат по ссылке работает, нет смысла использовать shared_ptr; просто верните ссылку на фактический объект. - person James Kanze; 30.01.2013
comment
@JamesKanze Не обязательно; на самом деле рекомендуемый способ передать shared_ptr в качестве параметра - это передать ссылку на константу. По крайней мере, так было до тех пор, пока не появилась семантика перемещения. Вы не заставляете вызывающего абонента хранить его как ссылку, он может сохранить копию, если намеревается хранить ее какое-то время. - person Ben Hymers; 30.01.2013
comment
@RamyAlZuhouri хорошо, в этом случае вам действительно не нужны shared_ptrs, если только вам не нужен какой-то способ убедиться, что возвращенный шейдер по-прежнему действителен через некоторое время, и в этом случае вы, вероятно, захотите сохранить для него weak_ptr. Если нет, вы хотите скопировать/переместить конструкторы/операторы присваивания и вернуться с помощью указателя const raw. - person Ben Hymers; 30.01.2013
comment
@BenHymers В качестве параметра я согласен. Но я сказал возврат по значению. - person James Kanze; 30.01.2013
comment
Мне нужно передать право собственности на указатель программе, но удалить объект может только программа. Я пришел из Objective-C, было бы намного проще с сильными/слабыми ссылками. - person Ramy Al Zuhouri; 30.01.2013
comment
@RamyAlZuhouri C++ также поддерживает сильные и слабые ссылки, но если вы действительно передаете право собственности, auto_ptr (или если вы уверены, что можете рассчитывать на C++11, unique_ptr) звучит более уместно. - person James Kanze; 30.01.2013
comment
@BenHymers - я не понимаю, что ты имеешь в виду. Как может класс, отвечающий за удаление объекта, сделать это, если он раздавал на него общие указатели? - person Bill; 30.01.2013
comment
@JamesKanze Вы сказали вернуть по ссылке, да, моя ошибка. Звучит так, как будто вы намекаете, что нет смысла когда-либо ссылаться на shared_ptr, что не так. Я бы не рекомендовал использовать auto_ptr, даже если C++11 недоступен; его трудно использовать правильно, и по какой-то причине он устарел в С++ 11 :) - person Ben Hymers; 31.01.2013
comment
@Bill Он сказал это после того, как я написал свой комментарий :) Да, если вы хотите удалить что-то только в одном месте, вы не должны раздавать ему shared_ptr. Я понял, что, поскольку вопрос был о shared_ptr, желательно совместное владение, и я просто советовал, чтобы совместное владение не было (или не должно быть) таким же, как неизвестное владение. - person Ben Hymers; 31.01.2013
comment
@BenHymers Несмотря на заявления об обратном, std::auto_ptr работает достаточно хорошо для того, для чего он был разработан. И вы знаете, что найдете его, даже если у вас нет последней версии компилятора. - person James Kanze; 31.01.2013
comment
@JamesKanze Действительно. Я просто сказал, что это сложно правильно использовать :) Возможно, не для нас с вами, но уж точно для тех, кто просит помощи с shared_ptr. - person Ben Hymers; 31.01.2013
comment
@BenHymers Да, если возможно, используйте boost::scoped_ptr или std::unique_ptr вместо std::auto_ptr. - person Bill; 31.01.2013

В любом случае здесь все в порядке.

Это член, поэтому безопасно возвращать ссылку на него, пока вызывающая сторона не удерживает его по ссылке, а затем уничтожает Program.

Если вы вернетесь по неконстантной ссылке, то вызывающая сторона может вызвать reset для вашего указателя, который вызовет glDeleteShader, если не существует других копий указателя. Как бы то ни было, он будет вести себя так, как вы хотите - Shader будет уничтожен только тогда, когда на него больше не будет ссылок.

Я бы лично вернул shared_ptr по значению, но это просто личное предпочтение.

РЕДАКТИРОВАТЬ: Предполагая, что вы имели в виду, что беспокоитесь о правильности при копировании Program (не Shader), не беспокойтесь об этом; у двух Program будут общие_ptr для одного и того же Shader, которые не будут уничтожены, пока не будут уничтожены оба Program. Это может быть или не быть тем, что вам нужно (вы можете захотеть выделить совершенно отдельный Shader для копии), но это безопасно.

person Ben Hymers    schedule 30.01.2013
comment
Что касается дизайна классов, как вы думаете, это нормально или я слишком усложняю? - person Ramy Al Zuhouri; 30.01.2013
comment
Я бы сказал, что вы недостаточно показали нам свой дизайн класса, чтобы мы могли это определить. - person Jonathan Grynspan; 30.01.2013

Я бы предпочел вернуть по значению. Если вы вернетесь по ссылке, вы можете столкнуться с висячей ссылкой на shared_ptr, если в какой-то момент экземпляр будет уничтожен, а какая-то переменная все еще содержит ссылку на shared_ptr.

Это как раз то, чего должны были избежать интеллектуальные указатели, но их подсчет ссылок работает безопасно только в том случае, если вы не возвращаете их путем копирования. Это сделает недействительным весь смысл использования здесь shared_ptr.

person Piotr99    schedule 30.01.2013

Не зная много об основных проблемах с общими указателями, я нашел этот пост Тьяго Масейры (предположительно, сотрудника Qt/Trolltech) довольно полезным для получения лучшего представления о том, следует ли возвращать значение или const&: http://lists.trolltech.com/qt-interest/2007-11/thread00209-0.html

Цитируя этот пост, вот основной аргумент для возврата значения из функции const:

Причина, по которой это:

T at(const Key& k) const;

вместо:

const T &at(const Key &k) const;

потому что так везде в Qt. Мы нигде не возвращаем refs-to-const. Чтобы вернуть ref-to-const, нам требуется, чтобы где-то существовала переменная, которая определяет внутреннюю структуру класса. Возвращая значение, мы можем делать все, что захотим внутри.

Однако я не уверен, применимо ли это к вашей проблеме... (Все еще учусь;))

person hcc23    schedule 30.01.2013
comment
Хороший момент: если он возвращается по ссылке, его можно изменить, чтобы он больше не был константой. - person Ramy Al Zuhouri; 30.01.2013
comment
... который определяет внутреннюю структуру класса, является важным моментом. ... потому что так везде в Qt - ужасный способ начать объяснение :) - person Ben Hymers; 30.01.2013
comment
@RamyAlZuhouri: обратите внимание, что функция возврата ссылки возвращает const T & (или в обозначении, которое я лично предпочитаю, T const &), то есть ссылку на константу; это означает, что хотя это ссылка, все, что обрабатывает возвращаемое значение, не может его изменить. - person hcc23; 30.01.2013