(на с ++ 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: Этот код не возвращает ссылку на 0x6000727b8
, а вместо этого создает копию, которая хранится в 0xffffcb40
, но конструктор копирования Product
не является называется, почему?
// Case (ii): Output reference parameter
Product product_param;
container.GetProductById ("B-003", product_param);
Вопрос 2: Я хотел бы предоставить доступ к объекту на карте, чтобы избежать создания копий объекта. Я должен также указать, с логическим значением или исключением, если Product
был найден. Я бы предпочел что-то вроде case (ii) : bool GetProductById (const std::string & id, Product & product)
, но это не приводит к константности.
Из трех случаев, которые я привел в коде, или из другого вашего предложения, как лучше всего это сделать?
Вопрос 3: В этом случае Product
, сохраненные на карте, не изменятся после их сохранения. Но если Product
нужно изменить после того, как они будут сохранены на карте, применимы ли подходы, подобные тем, которые приведены в вопросе 2? Является ли плохой практикой непосредственное изменение объектов значений карты? (Я знаю, что ключи должны быть константами).