Как клиенту отдавать класс А, содержащий weak_ptr, а в библиотеке использовать класс А с shared_ptr без дублирования кода?

Начну с наивных реализаций, чтобы подвести к сути вопроса. Допустим есть первая реализация двух классов. Думаю, тут задача понятна

class A
{
    string name_{};
public:
    A(string_view name): name_(name) {};
};

class B
{
    vector<A*> v_{}; // ВАРИАНТ 1
public:
    B() {};
    ~B() { for (auto& a : v_) delete a; };

    A* createA(string_view name) 
    { 
        v_.push_back(new A(name));
        return v_.back(); 
    }
};

Но отдавать клиенту сырые указатели плохо, поэтому следующий шаг - используем std::shared_ptr для хранения объектов класса A

class A
{
    string name_{};
public:
    A(string_view name) : name_(name) {};
};

class B
{
    vector<shared_ptr<A>> v_{}; // ВАРИАНТ 2
public:
    B() {};

    shared_ptr<A> createA(string_view name) 
    { 
        v_.push_back(make_shared<A>(name)); 
        return v_.back(); 
    }
};

Следующая наивная итерация - скрываем реализацию используя идиому PImpl

class A
{
    struct Impl;
    shared_ptr<Impl> pImpl; // СЛЕДУЮЩАЯ ИТЕРАЦИЯ
public:
    A(string_view name) /* : pImpl(make_shared<Impl>()) */ { };
    /*public API*/
};

class B
{
    vector<A> v_{};
public:
    B() {};

    A createA(string_view name)
    {
        v_.emplace_back(name);
        return v_.back();
    }
};

Теперь клиенту отдается объекты класса А, которые содержат shared_ptr на реализацию, которая скрыта от клиента.

Я ищу способ сделать еще одну итерацию данного процесса разработки. Дело в том, что в приложении будет намного больше двух классов, которые будут выстраиваться в древовидные структуры. Т.к. класс shared_ptr позволяет подсчитывать количество ссылок, хотелось бы использовать эту возможность. Но количество объектов, созданных клиентом меня не интересует, хотелось бы вести подсчет ссылок только в библиотеке. Т.е. хотелось бы клиенту отдавать класс А, содержащий weak_ptr, а в библиотеке использовать класс А с shared_ptr... Как я не пыталась реализовать - получается много дублирующего кода для поддержки двух видов класса А. Есть ли решение этой задачи на данном пути? Или же надо использовать какой либо другой паттерн проектирования?


Ответы (1 шт):

Автор решения: DmitryK

Как вариант - возможно будет проще во 2 варианте всегда возвращать weak_ptr для всех - и для библиотеки и для клиента? Доступа к объекту через weak_ptr нет, для доступа необходимо его преобразовывать в shared_ptr.

class B
{
    vector<shared_ptr<A>> v_{}; // ВАРИАНТ 2
public:
    weak_ptr<A> createA(string_view name) 
    { 
        v_.push_back(make_shared<A>(name)); 
        return weak_ptr<A> tmp( v_.back() ); 
    }
};

Или по той же причине в class A хранить weak_ptr. Когда библиотеке понадобиться - она преобразует в shared_ptr и счетчик увеличится. Но на мой взгляд делать обертку над оберткой - просто усложнение кода, т.к. shared_ptr (в комплекте с weak_ptr) - уже обертка над объектом, определяющая его время жизни.

Или сделать 2 фабричные функции - одну для библиотеки (не видна клиенту), вторую - для клиента

class B
{
    vector<shared_ptr<A>> v_{}; // ВАРИАНТ 2
    shared_ptr<A> createA(string_view name) // фабричная функция для библиотеки, недоступная клиенту
    { 
        v_.push_back(make_shared<A>(name)); 
        return v_.back(); 
    }
public:
    weak_ptr<A> GetA(string_view name) // фабричная функция для клиента
    { 
        return weak_ptr<A> tmp( createA(name) ); 
    }
};

И непонятно, как клиент использует данные объекта и вообще использует ли? Если использует, то ему всё равно нужно преобразовывать weak_ptr в shared_ptr - а это увеличит счетчик ссылок.

Но количество объектов, созданных клиентом меня не интересует, хотелось бы вести подсчет ссылок только в библиотеке.

Т.е. клиент может создавать объекты минуя class B::createA()? Это точно? Тогда у библиотеки и клиента получается два разных пула объектов и в каком-то месте нужно отдать клиенту копию объекта из библиотеки (shared_ptr на копию объекта) а не указатель на сам объект. Это точно то поведение, которого вы хотите добиться?

→ Ссылка