Владение или совместное использование ресурса между объектами - PullRequest
0 голосов
/ 26 августа 2018

A имеют коллекцию рабочих объектов следующего типа:

template<class T> struct Worker;

Различные рабочие, имеющие разные аргументы шаблона T, используют какой-то ресурс

struct Resource;

Resources, используемые разными 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

Я вижу два прямых способа сделать это:

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

Эти проблемыне влияет на правильность кода, но, вероятно, означает, что весь дизайн имеет недостатки.Я думаю, что должен быть намного лучший подход.Пожалуйста, объясните, каковы лучшие практики в таких случаях.(Я понимаю, что мой вопрос может быть слишком общим, и решение, которое мне нужно, может зависеть от того, как именно я создаю ресурсы и работников; хорошее решение может быть результатом другого взгляда на проблему, и в настоящее время я застрял в двух подходахЯ обрисовал выше.)

Полный код здесь .

1 Ответ

0 голосов
/ 26 августа 2018

Во втором случае ненужные подсчитанные ссылки остаются в ресурсе после создания всех рабочих.Я мог бы взять shared_ptr по значению и использовать std::move в последней конструкции Worker, но это потребовало бы от меня явного отслеживания последнего конструктора - отличный способ получить ошибку.

Нет,ты не;Создайте свои ресурсы с unique_ptr по умолчанию, при необходимости «ничего» не нужно конвертировать в shared_ptr.


Рассмотрите возможность использования только конструктора, который принимает std::shared_ptr значение ;std::shared_ptr имеет конструктор, который принимает значение r std::unique_ptr;

template<class T>
struct Worker
{
    Worker(T arg, std::shared_ptr<Resource> resource) :
        resource_(std::move(resource))
    { }

    std::shared_ptr<Resource> resource_;
};

void foo()
{
    auto resource = std::make_unique<Resource>();
    auto resource_shared = std::make_shared<Resource>();

    Worker worker1(0, std::move(resource));
    Worker worker2(0, resource_shared);
    Worker worker3(0, resource_shared);
    // worker2 and worker3 share *shared_resource


    Worker worker4(0, std::move(resource));  //resource is empty
}

Live Demo

...