Здравствуйте, хотелось бы услышать мнения знающих людей по поводу такого вопроса.
Необходимо предоставить пользователям библиотеки доступ к некоторому классу, при этом скрыв его реализацию и обеспечив максимум удобств в плане защиты от утечки ресурсов и т.п. Использование интерфейсного абстрактного класса отпадает, т.к. необходимо обеспечить возможность наследования.
Какой из следующих двух вариантов выбрать?
1) На реализацию класса TSomeClass, представленную классом TSomeClassImpl, указывает private поле m_Impl типа умный указатель (обеспечивает подсчёт ссылок и автоматическое удаление объекта при обнулении счётчика).
Пользователь получает экземпляр класса TSomeClass посредством вызова функции GetSomeClass() и работает с ним, не заботясь об удалении динамически созданного поля m_Impl.
Вот код:
class TSomeClassImpl;
class TSomeClass
{
public:
TSomeClass(TSomeClassImpl* Impl) : m_Impl(Impl){ /* Ещё какие-то действия */ }
// ...private:
shared_ptr<TSomeClassImpl> m_Impl;
};
TSomeClass GetSomeClass();
2) На реализацию класса указывает простой указатель, но функция GetSomeClass() возвращает умный указатель. Пользователь опять же не заботится об удалении динамически созданного поля m_Impl, т.к. это обеспечивает деструктор класса TSomeClass.
Код:
class TSomeClassImpl;
class TSomeClass
{
public:
TSomeClass(TSomeClassImpl* Impl) : m_Impl(Impl){ /* Ещё какие-то действия */ }
~TSomeClass(){ delete m_Impl; }
// ...private:
TSomeClassImpl* m_Impl;
};
shared_ptr<TSomeClass> GetSomeClass();
Я вижу следующие преимущества каждого из подходов.
Для первого:
1. При возникновении исключения в конструкторе TSomeClass не нужно заботиться об удалении содержимого m_Impl (для второго подхода этот вопрос остаётся открытым).
2. В деструкторе TSomeClass не нужно удалять m_Impl.
3. Пользователю не надо разбираться с умными указателями.
Для второго:
1. Тип возвращаемого значения функции GetSomeClass() документирует, что утечек ресурсов не произойдёт, т.к. пользователь оперирует полученным объектом через умный указатель.
2. Возможность предварительного создания пользователем переменной типа shared_ptr<TSomeClass>, по умолчанию инициализируемой нулевым указателем, с последующим присвоением ей результата вызова GetSomeClass() (для первого варианта учёт возможности создания неинициализированной переменной ведёт к тому, что почти в каждом методе TSomeClass нужно обеспечить проверку валидности m_Impl, а также обеспечить открытый метод для проверки пользователем инициализированности объекта).
3. Все временные объекты, создаваемые и удаляемые при возврате значения функцией GetSomeClass(), являются достаточно компактными shared_ptr (а для первого варианта копируются объекты типа TSomeClass, хотя это также не очень накладно, т.к. по-хорошему кроме поля m_Impl класс не должен больше ничего содержать, а затраты на подсчёт ссылок будут одинаковыми в обоих случаях).
Сам я склоняюсь к первому варианту, но товарищи используют второй и советуют его. Что скажете?
Re: Использование умных указателей при сокрытии реализации
Голосую за первый вариант но с одной добавочкой
возвращать указатель на TSomeClass чтобы избежать лишнего копирования
TSomeClass* GetSomeClass() { return m_Impl->get(); }
!0xDEAD
Re[2]: Использование умных указателей при сокрытии реализаци
Здравствуйте, Какая разница, Вы писали:
КР>Здравствуйте, Yoxel, Вы писали:
КР>Голосую за первый вариант но с одной добавочкой КР>возвращать указатель на TSomeClass чтобы избежать лишнего копирования КР>TSomeClass* GetSomeClass() { return m_Impl->get(); }
Здравствуйте, minorlogic, Вы писали: M>Рассмотрите еще один вариант , который описывает дедушка страуструп.
Вы предлагаете использовать абстрактный класс в качестве интерфейса, а реализацию обеспечить наследованием от него? Как я отмечал вначале, необходимо обеспечить порождение от TSomeClass новых классов (с сохранением реализации и добавлением новых методов), например:
class TSomeClassCustom : public TSomeClass
{
public:
void CustomMethod1();
// ...
};
а это невозможно в Вашем случае.
Re: Использование умных указателей при сокрытии реализации
Здравствуйте, Yoxel, Вы писали:
Y>Необходимо предоставить пользователям библиотеки доступ к некоторому классу, при этом скрыв его реализацию и обеспечив максимум удобств в плане защиты от утечки ресурсов и т.п. Использование интерфейсного абстрактного класса отпадает, т.к. необходимо обеспечить возможность наследования. Y>Какой из следующих двух вариантов выбрать?
[skipped]
Эти подходы по смыслу разные, между ними нельзя выбирать как между эквивалентными решениями, надо применять тот или другой в зависимости от ситуации.
1-й вариант — это если тебе нужно получить value-семантику для класса, который нельзя копировать, а указатель (в т.ч. и smart) использовать нежелательно. Для этого создаётся handle-класс, управляющий временем жизни экземпляра целевого класса (в данном случае через счётчик ссылок shared_ptr).
Во 2-м случае сам класс должен быть noncopyable, конструктор желательно поместить в protected/private, производящую функцию сделать static-членом класса. Такой класс имеет смысл использовать как базовый. Поэтому деструктор надо также сделать виртуальным. То есть реально производящая функция будет создавать объект дочернего класса, а возвращать указатель на базовый, обёрнутый в shared_ptr для управления временем жизни.
There is nothing that cannot be solved through sufficient application of brute force and ignorance.
Re: Использование умных указателей при сокрытии реализации
Вопрос возникающий сам собой, зачем нужна функция GetSomeClass? Наверника, функция GetSomeClass должна принимать некоторые параметры, которые определяются внешним кодом, тогда не проще сделать
namespace Private {
class TSomeClassImpl;} /// зачем засорять пространство имен библиотеки особенностями реализации?class TSomeClass
{
staticsmart_ptr <Private::TSomeClassImpl> GetImpl (/*параметры из GetSomeClass*/);
public:
TSomeClass (/*параметры из GetSomeClass*/) : m_Impl (GetImpl (/*параметры из GetSomeClass*/)) {}
~TSomeClass (); /// обязан быть деструктор, а то это уже не сокрытие реализации - все упадет во время линковкиprivate:
smart_ptr <Private::TSomeClassImpl> m_Impl;
};
, действительно скрыть реализацию, и сделать использование твоего объекта прозрачным для пользователя, без использования GetSomeClass?
Y>2) На реализацию класса указывает простой указатель, но функция GetSomeClass() возвращает умный указатель. Пользователь опять же не заботится об удалении динамически созданного поля m_Impl, т.к. это обеспечивает деструктор класса TSomeClass.
Фишка в том, что твой класс должен инкапсулировать данные о своей внутренней структуре, и пользователя вообще не должно волновать наличие или отсутствие m_Impl;
Y>Сам я склоняюсь к первому варианту, но товарищи используют второй и советуют его. Что скажете?
Почему советуют?
Используй умные указатели (снимает много головной боли), но если так хочеться использовать "голый" указатель — используй его, но не нужно об этом сообщать в интерфейсе класса, на то он и интерфейс, что бы скрывать реализацию;
ps: Посмотри по ключевому слову PIMPL на Guru of the Week, подробнее описано хорошим автором. Можно ещё поиском на сайте воспользоваться, тема достаточно частая;
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
"В любое мгновение принятия решения, лучшее, что вы можете сделать, это принять правильное решение; следующим лучшим вариантом будет принять неправильное решение, худший вариант – не принимать решения совсем" (c) Теодор Рузвельт.
Re[3]: Использование умных указателей при сокрытии реализаци
Здравствуйте, Какая разница, Вы писали:
КР>>Голосую за первый вариант но с одной добавочкой КР>>возвращать указатель на TSomeClass чтобы избежать лишнего копирования КР>>TSomeClass* GetSomeClass() { return m_Impl->get(); }
КР>А еще лучше реализовать оператор ->
Хотелось бы избавить пользователя от операций с указателями, чтобы не возникало вопросов, кто и когда должен удалять объекты.
Кроме того, мне следовало уточнить, что в моей задаче функция GetSomeClass() не является методом TSomeClass, это метод другого класса (назовём его TKernel), через который пользователь может получить нужные ему экземпляры TSomeClass, поэтому её реализация выглядит примерно так:
TSomeClass TKernel::GetSomeClass()
{
return new TSomeClassImpl; // В реальности TSomeClassImpl имеет параметры.
}
Re[2]: Использование умных указателей при сокрытии реализаци
Здравствуйте, load runner, Вы писали:
LR>Эти подходы по смыслу разные, между ними нельзя выбирать как между эквивалентными решениями, надо применять тот или другой в зависимости от ситуации.
Спасибо за разъяснения. Вообще-то, я решаю проблему предоставления пользователю моей библиотеки ограниченного доступа к информации, хранящейся в объекте модели типа TKernel. Для этого предоставляю класс TSomeClass, экземпляры которого пользователь создать не может, а пользуется для их получения методами типа TSomeClass TKernel::GetSomeClass().
При этом пользователь может копировать эти экземпляры (копии будут делить один и тот же объект-реализацию).
Похожего поведения я могу добиться обоими способами (во втором случае пользователь оперирует объектами типа shared_ptr<TSomeClass>).