Здравствуйте, хотелось бы услышать мнения знающих людей по поводу такого вопроса.
Необходимо предоставить пользователям библиотеки доступ к некоторому классу, при этом скрыв его реализацию и обеспечив максимум удобств в плане защиты от утечки ресурсов и т.п. Использование интерфейсного абстрактного класса отпадает, т.к. необходимо обеспечить возможность наследования.
Какой из следующих двух вариантов выбрать?
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 класс не должен больше ничего содержать, а затраты на подсчёт ссылок будут одинаковыми в обоих случаях).
Сам я склоняюсь к первому варианту, но товарищи используют второй и советуют его. Что скажете?