C ++ 11 Лучшие практики для кэширования в std :: map? - PullRequest
0 голосов
/ 06 ноября 2018

(на с ++ 11)

Я хочу хранить на карте объекты (Product), которые достаточно дороги для вычисления. Эти объекты не дешевы, поэтому я не хочу создавать ненужные копии. Карта принадлежит классу Container, который даст доступ к объектам. Я сделал пример, который представляет то, что у меня есть (код также находится здесь: запустить онлайн ):

#include <iostream>
#include <map>
#include <string>
#include <utility>

class Product
{
private:
  std::string m_id, m_name;

public:
  Product () : m_id (), m_name () { std::cout << "\tProduct default constructor.\n"; }

  Product (const std::string & id, const std::string & name) : m_id (id), m_name (name)
  {
    std::cout << "\tProduct parameters constructor <" << m_id << ", " << m_name << ">.\n";
  }

  Product (const Product & copy) : m_id (copy.m_id), m_name (copy.m_name)
  {
    std::cout << "\tProduct copy constructor.\n";
  }
};

class Container
{
private:
  std::map<std::string, Product> m_products_cache;

public:
  Container () : m_products_cache () { }
  Container (const Container & copy) : m_products_cache (copy.m_products_cache) { }

  Container (const std::string & filename) : Container ()
  {
    // Simulate reading file and storing its contents in map with this:
    m_products_cache.insert (std::pair<const std::string, Product> ("A-001", Product ("A-001", "Product 1")));
    m_products_cache.insert (std::pair<const std::string, Product> ("A-002", Product ("A-002", "Product 2")));
  }

  const Product &
  CreateNewProduct (const std::string & id, const std::string & name)
  {
    std::map<std::string, Product>::iterator product_it = m_products_cache.find (id);

    if (product_it != m_products_cache.end ())
      return product_it->second; // Returns a const-reference to the Product

    std::pair<std::map<std::string, Product>::iterator, bool> inserted_it;
    inserted_it = m_products_cache.insert (std::pair<const std::string, Product> (id, Product (id, name)));

    return inserted_it.first->second; // Returns a const-reference to the Product
  }

  // Case (i)  :  Returns a const-reference.
  const Product &
  GetProductById (const std::string & id) const
  {
    std::map<std::string, Product>::const_iterator product_it = m_products_cache.find (id);

    if (product_it == m_products_cache.end ())
      throw std::out_of_range ("Error: Product ID'" + id + "' NOT found.\n");

    return product_it->second;
  }

  // Case (ii)  :  Uses an non-const reference parameter to return the Product, if found.
  bool
  GetProductById (const std::string & id, Product & product) const
  {
    std::map<std::string, Product>::const_iterator product_it = m_products_cache.find (id);

    if (product_it == m_products_cache.end ()) return false;

    product = product_it->second;
    return true;
  }

  // Case (iii)  :  Uses a const Product non-const pointer reference to expose the Product, if found.
  bool
  GetProductById (const std::string & id, const Product *& product) const
  {
    std::map<std::string, Product>::const_iterator product_it = m_products_cache.find (id);

    if (product_it == m_products_cache.end ())
      {
        product = nullptr;
        return false;
      }

    product = &product_it->second;
    return true;
  }
};

int
main (int argc, char **argv)
{
  Container container ("ignored_filename");

  const Product & created_product = container.CreateNewProduct ("B-003", "Product 3");
  std::cout << "\n - Created Product located at " << &created_product << "\n\n";

  std::cout << " - Obtain reference to Product in container:\n\n";

  const Product & product_reference = container.GetProductById ("B-003"); // Gets product at expected address.
  std::cout << "\tCase i  : product located at " << &product_reference
            << (&product_reference == &created_product ? " (Same object)" : " (Different object)") << "\n";

  Product product_param;
  container.GetProductById ("B-003", product_param); // Question 1: Gets a copy, but doesn't call copy constructor, WHY?
  std::cout << "\tCase ii : product located at " << &product_param
            << (&product_param == &created_product ? " (Same object)" : " (Different object)") << "\n";

  const Product *product_pointer_param;
  container.GetProductById ("B-003", product_pointer_param); // Gets product at expected address.
  std::cout << "\tCase iii: product located at " << product_pointer_param
            << (product_pointer_param == &created_product ? " (Same object)" : " (Different object)") << "\n";
}

Это дает следующий вывод:

 - Created Product located at 0x6000727b8

 - Obtain reference to Product in container:

    Case i  : product located at 0x6000727b8 (Same object)
    Product default constructor.
    Case ii : product located at 0xffffcb40 (Different object)
    Case iii: product located at 0x6000727b8 (Same object)

У меня есть следующие вопросы:

  1. Вопрос 1: Этот код не возвращает ссылку на 0x6000727b8, а вместо этого создает копию, которая хранится в 0xffffcb40, но конструктор копирования Product не является называется, почему?

    // Case (ii): Output reference parameter
    Product product_param;
    container.GetProductById ("B-003", product_param);
    
  2. Вопрос 2: Я хотел бы предоставить доступ к объекту на карте, чтобы избежать создания копий объекта. Я должен также указать, с логическим значением или исключением, если Product был найден. Я бы предпочел что-то вроде case (ii) : bool GetProductById (const std::string & id, Product & product), но это не приводит к константности.

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

  3. Вопрос 3: В этом случае Product, сохраненные на карте, не изменятся после их сохранения. Но если Product нужно изменить после того, как они будут сохранены на карте, применимы ли подходы, подобные тем, которые приведены в вопросе 2? Является ли плохой практикой непосредственное изменение объектов значений карты? (Я знаю, что ключи должны быть константами).

1 Ответ

0 голосов
/ 06 ноября 2018
  1. У вас есть 2 объекта: product_param и объект, сохраненный на карте. Когда вы присваиваете product_param через ссылку product, он будет использовать оператор присваивания Product::operator=(const Product&) (который предоставлен вам компилятором, потому что вы не сказали этого не делать), чтобы назначить содержимое. У вас все еще есть 2 объекта, и у каждого из них есть свой идентификатор и имя.

  2. case (i) подходит для случая, когда выдается исключение. case (iii) немного неловко, но работает. Другой вариант - const Product* GetProductById(const std::string& id) const. Это может вернуть указатель на объект, если он существует, или вернуть nullptr, если его нет. К сожалению, этот прототип имеет тот же прототип, что и case (i) , кроме возвращаемого значения, поэтому вам нужно либо иметь другое имя функции, либо как-то придумать какой-то другой способ различать, как их вызывать.

    Стандарт обычно делает это, используя operator[](const Key& key) для версии, которая не выдает, и at(const Key& key) для версии, которая делает. Для одного будет немного неудобно возвращать указатель, а для другого - ссылка.

  3. Это действительно зависит от вас. Ничто не мешает вам изменить значения карты, но если вы не хотите, чтобы пользователь мог это сделать, вы всегда можете вернуть константные ссылки или указатели, чтобы вернуть их пользователю.

...