Использование умных указателей при сокрытии реализации
От: 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 класс не должен больше ничего содержать, а затраты на подсчёт ссылок будут одинаковыми в обоих случаях).

Сам я склоняюсь к первому варианту, но товарищи используют второй и советуют его. Что скажете?
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.