Подтвердить что ты не робот

Каков правильный способ разоблачения ресурсов, принадлежащих классу?

Скажем, у меня есть библиотека, которая имеет класс Document. Экземпляр Document может иметь несколько экземпляров Field. Field имеет несколько подклассов (например, IntegerField и StringField), и даже пользователь API может подклассифицировать его и предоставить экземпляры подкласса на Document (допустим, пользователю разрешено создавать пользовательский тип данных для хранения в поле).

Я хотел бы предоставить экземпляры Field, принадлежащие Document, через API таким образом, чтобы пользователи могли взаимодействовать с ними, но без передачи права собственности.

Каков правильный способ сделать это?

Я думал о:

  • Предоставление ссылки const std::unique_ptr<Field>& - это кажется довольно уродливым
  • Предоставление простого указателя Field* - это не так, потому что пользователь может быть уверен, удастся ли ему удалить экземпляр или нет.
  • Использование std::shared_ptr вместо этого - это плохо, потому что владение не действительно разделяется.

Например,

class Document {
private:
    std::map<std::string, std::unique_ptr<Field> > fields;
    // ...
public:
    // ...

    // How is this done properly?
    const std::unique_ptr<Field> &field(const std::string &name) {
        return fields[name];
    }
}

Я с нетерпением жду ваших предложений. (Я также приветствую советы об альтернативных подходах, например, что предложил @Fulvio.)

4b9b3361

Ответ 1

Как ответили другие специалисты с технической точки зрения, я хотел бы указать вам на другой подход и пересмотреть ваш дизайн. Идея состоит в том, чтобы попытаться уважать Закон Деметры и не предоставлять доступ к субкомпонентам объекта. Это немного сложнее, и без конкретного примера я не могу предоставить много деталей, но попытаюсь представить классную книгу, составленную из страниц. Если я хочу напечатать одну, две или более страницы книги, с вашим текущим дизайном я могу сделать:

auto range = ...;
for( auto p : book.pages(range) )
  {
  p->print();
  }

соблюдая Деметру, вы будете

auto range = ...;
book.print( /* possibly a range here */ );

это наклон к лучшему инкапсуляции, так как вы не полагаетесь на внутренние детали книжного класса, и если его внутренняя структура изменяется, вам не нужно ничего делать с вашим кодом клиента.

Ответ 2

Я бы вернул Bar& (возможно, с помощью const).

Пользователю нужно будет понять, что ссылка будет недействительной, если элемент будет удален с карты, но это будет так, как вы делаете, из-за модели с одним владельцем.

Ответ 3

Я обычно возвращаю ссылки на данные, а не ссылку на unique_ptr:

const Bar &getBar(std::string name) const {
    return *bars[name];
}

Если вы хотите вернуть пустой элемент, вы можете вернуть необработанный указатель (и nullptr в случае пустого). Или еще лучше вы можете использовать boost::optional (std::optional в С++ 14).

Если существует вероятность того, что эта ссылка сохранится дольше, чем владелец (многопоточная среда), я использую shared_ptr внутри себя и возвращаю weak_ptr в методах доступа.

Ответ 4

Я бы вернул std::weak_ptr< Bar > (если С++ 11) или boost::weak_ptr (если не С++ 11). Это делает его явным, что это куча памяти и не рискует обмануть ссылку на несуществующую память (например, Bar &). Это также делает владение явным.

Ответ 5

Мне вообще не нравится раздавать ссылки на внутренние члены. Что произойдет, если другой поток изменит его? Вместо этого, если я не могу передать копию, я предпочитаю более функциональный подход. Что-то вроде этого.

class Foo {
private:
    std::map<std::string, std::unique_ptr<Bar> > bars;

    // ...

public:

    // ...
    template<typename Callable> void withBar(const std::string& name, Callable call) const {
        //maybe a lock_guard here?
        auto iter = bars.find(name);
        if(iter != std::end(bars)) {
            call(iter->second.get());
        }
    }
}

Таким образом, собственная внутренняя структура никогда не "оставляет" принадлежащий ей класс, а владелец может контролировать инварианты. Также может иметь тонкости, например, сделать код noop, если запрошенная запись не существует. Может использовать его как,

myfoo.withBar("test", [] (const Bar* bar){
   bar->dostuff();
});

Ответ 6

Если возможно, я попытался бы вообще не подвергать внутренности Document, но при этом я не вернул бы const Field&, или если вам нужно, чтобы он был нулевым a const Field*. Оба ясно показывают, что Document сохраняет право собственности. Также сделайте сам метод const.

У вас может быть версия non const метода, которая возвращает Field& или Field*, но я бы избегал этого, если вы можете.

Ответ 7

Это еще не рассмотрено, я думаю: верните прокси-объект с тем же интерфейсом.

Владелец прокси-объекта имеет право собственности на этот прокси-объект. Прокси-сервер содержит любую ссылку (возможно, слабую) на объект, на который ссылается. Когда объект будет удален, вы можете вызвать некоторую ошибку. У объекта eproxy нет права собственности.

Возможно, это уже упоминалось. Я не очень хорошо знаком с С++.