Дизайн API для извлечения объекта из контейнера, который может не существовать - PullRequest
0 голосов
/ 27 июня 2018

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

Пока я думал о:

  1. Брось исключение

    T get(int index) const
    {
        if(not_exists(index)) throw std::out_of_range("Index is out of range");
        return get_base(index);
    }
    
  2. Построить T и вернуть его

    T get(int index) const
    {
        if(not_exists(index)) return T{};
        return get_base(index);
    }
    
  3. Возврат bool и получение в качестве ссылки

    bool get(int index, T & obj) const
    {
        if(not_exists(index)) return false;
        obj = get_base(index); return true;
    }
    
  4. Использовать аргумент по умолчанию, если не найден

    T get(int index, T def_obj) const
    {
        if(not_exists(index)) return def_obj;
        return get_base(index);
    }
    
  5. Комбинат 4 + 2

    T get(int index, T def_obj = {}) const
    {
        if(not_exists(index)) return def_obj;
        return get_base(index);
    }
    
  6. Изменить контейнер для добавления такого объекта (предупреждение - get больше не будет const!)

    T get(int index, T def_obj = {})
    {
        if(not_exists(index)) set(index, def_obj);
        return get_base(index);
    }
    

Каковы плюсы и минусы каждого решения? Я что-то пропустил?

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

Ответы [ 2 ]

0 голосов
/ 29 июня 2018

Фундаментальная проблема здесь заключается в семантике : # 1 и # 3 - единственные, где присутствие отличается от отсутствия; # 6 всегда удается вернуть элемент контейнера; а другим всегда удается вернуть значение . Приложение определяет, какие из них вам нужны.

В этом отношении # 1 и # 3 complete : одного из них достаточно для реализации любого другого (учитывая некоторые другие средства добавления элементов для эмуляции # 6). Если можно избежать помех от других потоков, то # 4 и # 5 одинаково мощны: их можно использовать для обнаружения отсутствия, предлагая два разных значения по умолчанию. В качестве альтернативы можно добавить bool contains(int index) const;, чтобы можно было различать отсутствие (опять же, при необходимости, с внешней синхронизацией).

Однако эти эмуляции (кроме # 2/4/5 из # 1/3) предполагают повторные поиски, которые могут иметь неадекватную производительность. Для некоторых базовых структур данных для обеспечения максимальной производительности могут потребоваться другие операции: например, перемещение элемента из одного индекса в другой без его реконструкции.

Между тем, все эти подходы имеют практические проблемы, по крайней мере, в общем контексте.

  1. Некоторые эксперты считают, что logic_error всегда является ошибкой ; безусловно, исключение в достаточно распространенном случае обходится дорого. Однако здесь можно вернуть ссылку, что очень полезно.
  2. T должно быть конструктивно (аналогично, но не идентично по умолчанию -конструктивно).
  3. T должен быть назначаемым (и клиент должен был создать его, возможно, использовать для нескольких вызовов). Неопределенное поведение может быть результатом игнорирования флага (поэтому отметьте его [[nodiscard]]).
  4. Два T объекта должны быть построены за вызов. По умолчанию можно сделать ссылку, чтобы разрешить возврат ссылки (и поддерживать громоздкую форму обнаружения пропущенных значений), но чтобы избежать разрешения временных аргументов, потребуется перегрузка rvalue-reference (или ограниченный шаблон).
  5. Как и # 4, это создает два объекта. В контексте шаблона улучшение # 2 в том, что значение-конструктивность требуется, только если значение по умолчанию used .
  6. Это может, конечно, иметь три варианта, как # 2/4/5. Было бы более полезно, если бы он возвращал ссылку (например, map::operator[]), чтобы разрешить изменение (потенциально) нового элемента.

Если T может быть дорогим для построения (даже из {}), только # 1 (как используется map::at) и предложение optional эффективны; удобно они также завершены. Возможно, самый быстрый вариант - вернуть const T*, используя нулевой указатель, указывающий на отсутствие. Выбор между ними - это вопрос тонких компромиссов производительности (если только в вашем магазине нет исключений или указателей в целом). Для дешевого T, # 5 привлекателен, если его семантика достаточна; иначе # 3 может быть лучшим (из-за сходства с if(std::cin >> x)).

0 голосов
/ 27 июня 2018

Попробуйте этот фрагмент (или std :: необязательный в C ++ 17)

boost::optional<T> get(int index, T& obj)
{
    if(not_exists(index))
        boost::none;
    else
        return get_base(index);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...