Использование умных указателей при сокрытии реализации
От: Yoxel  
Дата: 02.11.06 11:42
Оценка:
Здравствуйте, хотелось бы услышать мнения знающих людей по поводу такого вопроса.
Необходимо предоставить пользователям библиотеки доступ к некоторому классу, при этом скрыв его реализацию и обеспечив максимум удобств в плане защиты от утечки ресурсов и т.п. Использование интерфейсного абстрактного класса отпадает, т.к. необходимо обеспечить возможность наследования.
Какой из следующих двух вариантов выбрать?

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: Использование умных указателей при сокрытии реализации
От: Какая разница Украина  
Дата: 02.11.06 12:24
Оценка:
Здравствуйте, Yoxel, Вы писали:


Голосую за первый вариант но с одной добавочкой
возвращать указатель на TSomeClass чтобы избежать лишнего копирования
TSomeClass* GetSomeClass() { return m_Impl->get(); }
!0xDEAD
Re[2]: Использование умных указателей при сокрытии реализаци
От: Какая разница Украина  
Дата: 02.11.06 12:27
Оценка:
Здравствуйте, Какая разница, Вы писали:

КР>Здравствуйте, Yoxel, Вы писали:



КР>Голосую за первый вариант но с одной добавочкой

КР>возвращать указатель на TSomeClass чтобы избежать лишнего копирования
КР>TSomeClass* GetSomeClass() { return m_Impl->get(); }

А еще лучше реализовать оператор ->


const TSomeClass* operator ->() const { return m_Impl->get(); }
TSomeClass* operator ->() { return m_Impl->get(); }
!0xDEAD
Re: Использование умных указателей при сокрытии реализации
От: minorlogic Украина  
Дата: 02.11.06 12:32
Оценка:
Здравствуйте, Yoxel, Вы писали:

Рассмотрите еще один вариант , который описывает дедушка страуструп.


class TSomeClass
{
public:
  
  static shared_ptr<TSomeClass> create();

  virtual function1() = 0;    
  virtual function2() = 0;    

};
Ищу работу, 3D, SLAM, computer graphics/vision.
Re[2]: Использование умных указателей при сокрытии реализаци
От: Yoxel  
Дата: 02.11.06 12:52
Оценка:
Здравствуйте, minorlogic, Вы писали:
M>Рассмотрите еще один вариант , который описывает дедушка страуструп.

Вы предлагаете использовать абстрактный класс в качестве интерфейса, а реализацию обеспечить наследованием от него? Как я отмечал вначале, необходимо обеспечить порождение от TSomeClass новых классов (с сохранением реализации и добавлением новых методов), например:
class TSomeClassCustom : public TSomeClass
{
public:
  void CustomMethod1();
  // ...
};

а это невозможно в Вашем случае.
Re: Использование умных указателей при сокрытии реализации
От: load runner Россия  
Дата: 02.11.06 12:54
Оценка:
Здравствуйте, 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: Использование умных указателей при сокрытии реализации
От: np9mi7 Россия  
Дата: 02.11.06 13:01
Оценка:
Здравствуйте, Yoxel, Вы писали:

Y>
Y>class TSomeClassImpl;

Y>class TSomeClass
Y>{
Y>public:
Y>  TSomeClass(TSomeClassImpl* Impl) : m_Impl(Impl){ /* Ещё какие-то действия */ }
Y>  // ...

Y>private:
Y>  shared_ptr<TSomeClassImpl> m_Impl;
Y>};

Y>TSomeClass GetSomeClass();
Y>


Вопрос возникающий сам собой, зачем нужна функция GetSomeClass? Наверника, функция GetSomeClass должна принимать некоторые параметры, которые определяются внешним кодом, тогда не проще сделать
namespace Private {
class TSomeClassImpl;} /// зачем засорять пространство имен библиотеки особенностями реализации?

class TSomeClass
{
  static smart_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]: Использование умных указателей при сокрытии реализаци
От: Yoxel  
Дата: 02.11.06 13:23
Оценка:
Здравствуйте, Какая разница, Вы писали:

КР>>Голосую за первый вариант но с одной добавочкой

КР>>возвращать указатель на TSomeClass чтобы избежать лишнего копирования
КР>>TSomeClass* GetSomeClass() { return m_Impl->get(); }

КР>А еще лучше реализовать оператор ->


Хотелось бы избавить пользователя от операций с указателями, чтобы не возникало вопросов, кто и когда должен удалять объекты.
Кроме того, мне следовало уточнить, что в моей задаче функция GetSomeClass() не является методом TSomeClass, это метод другого класса (назовём его TKernel), через который пользователь может получить нужные ему экземпляры TSomeClass, поэтому её реализация выглядит примерно так:
TSomeClass TKernel::GetSomeClass()
{
  return new TSomeClassImpl; // В реальности TSomeClassImpl имеет параметры.
}
Re[2]: Использование умных указателей при сокрытии реализаци
От: Yoxel  
Дата: 02.11.06 13:39
Оценка:
Здравствуйте, load runner, Вы писали:

LR>Эти подходы по смыслу разные, между ними нельзя выбирать как между эквивалентными решениями, надо применять тот или другой в зависимости от ситуации.


Спасибо за разъяснения. Вообще-то, я решаю проблему предоставления пользователю моей библиотеки ограниченного доступа к информации, хранящейся в объекте модели типа TKernel. Для этого предоставляю класс TSomeClass, экземпляры которого пользователь создать не может, а пользуется для их получения методами типа TSomeClass TKernel::GetSomeClass().
При этом пользователь может копировать эти экземпляры (копии будут делить один и тот же объект-реализацию).
Похожего поведения я могу добиться обоими способами (во втором случае пользователь оперирует объектами типа shared_ptr<TSomeClass>).
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.