A имеют коллекцию рабочих объектов следующего типа:
template<class T> struct Worker;
Различные рабочие, имеющие разные аргументы шаблона T
, используют какой-то ресурс
struct Resource;
Resource
s, используемые разными Worker
, имеют одинаковый тип, но, возможно, разные значения.Ресурсы в целом тяжелые, следует избегать ненужных копий.Ресурсы используются только работниками, некоторые используются исключительно, некоторые распределяются между ними (эта информация доступна во время компиляции).Если ресурс используется исключительно, я хочу, чтобы Worker
был (возможно, эффективно) владельцем его, в противном случае он должен хранить ссылку.В псевдокоде я хочу сделать либо
Resource resource1(...);
Worker<Type1> worker1(..., std::move(resource1));
// worker1 owns resource1
Resource resource2(...);
Worker<Type2> worker2(..., std::move(resource2));
// worker2 owns resource2
или
Resource resource(...);
Worker<Type1> worker1(..., resource);
Worker<Type2> worker2(..., resource);
// workers use resource, but do not own it
Я вижу два прямых способа сделать это:
Используйте параметр шаблона, чтобы определить тип элемента данных в Worker
, который представляет ресурс:
template<class T, class R>
struct Worker
{
Worker(T arg, R resource) :
resource_(std::forward<R>(resource))
{ }
R resource_;
};
template<class T, class R>
Worker(T arg, R&& resource) -> Worker<T, R>;
void foo()
{
Resource r;
Worker worker1(0, r);
Worker worker2(0, r);
// type of worker1.resource_ is Resource&,
// type of worker2.resource_ is Resource&,
// both store a reference to r
Worker worker3(0, Resource{});
// type of worker3.resource_ is Resource,
// Resource{} is moved into it
}
Использование std::shared_ptr
:
template<class T>
struct Worker
{
Worker(T arg, const std::shared_ptr<Resource>& resource) :
resource_(resource)
{ }
template<class R, typename = std::enable_if_t<std::is_same_v<R, Resource>>>
Worker(T arg, R&& resource) :
resource_(std::make_shared<Resource>(std::move(resource)))
{ }
std::shared_ptr<Resource> resource_;
};
void foo()
{
auto resource = std::make_shared<Resource>();
Worker worker1(0, resource);
Worker worker2(0, resource);
// worker1 and worker2 share *resource_
Worker worker3(0, Resource{});
// worker3 effectively owns *resource_
}
Эти подходы, очевидно, очень разные, но в данном случае они эффективно эквивалентны.Мне они оба не нравятся.
В первом случае мне не нравится, что ресурс хранится за пределами Worker
s в некоторой локальной переменной.Существует потенциальный риск получить висячую ссылку в Worker
.
Во втором случае ненужная подсчитанная ссылка остается в resource
после того, как все Worker
s созданы.Я мог бы взять shared_ptr
по значению и использовать std::move
в последней Worker
конструкции, но для этого потребовалось бы явное отслеживание последнего конструктора - отличный способ получить ошибку.
Эти проблемыне влияет на правильность кода, но, вероятно, означает, что весь дизайн имеет недостатки.Я думаю, что должен быть намного лучший подход.Пожалуйста, объясните, каковы лучшие практики в таких случаях.(Я понимаю, что мой вопрос может быть слишком общим, и решение, которое мне нужно, может зависеть от того, как именно я создаю ресурсы и работников; хорошее решение может быть результатом другого взгляда на проблему, и в настоящее время я застрял в двух подходахЯ обрисовал выше.)
Полный код здесь .