(In c++ 11)
I want to store in a map objects (Product) that are somewhat expensive to compute. These objects aren't space cheap so I don't want to create unnecessary copies. The map belongs to a Container class that will give access to the objects. I made an example that represents what I have (the code is also found here: run online):
#include <iostream>
#include <map>
#include <string>
#include <utility>
class Product
{
private:
std::string m_id;
std::string 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 (std::string, std::string) : parameters constructor <" << m_id << ", " << m_name << ">.\n";
}
Product (const Product & copy) : m_id (copy.m_id), m_name (copy.m_name)
{
std::cout << "\tProduct (const Product &) : copy constructor.\n";
}
const std::string & GetId () const { return m_id; }
const std::string & GetName () const { return m_name; }
void ChangeName (const std::string & new_name) { m_name = new_name; }
void Print (std::ostream & os) const
{
os << "Product <ID '" << m_id << "' : Name '" << m_name << "'>";
}
friend std::ostream & operator<< (std::ostream & os, Product const & obj);
};
std::ostream &
operator<< (std::ostream & os, Product const & obj)
{
return os << "Product <ID '" << obj.m_id << "' : Name '" << obj.m_name << "'>";
}
class Container
{
private:
std::map<std::string, Product> m_products_cache;
public:
Container () : m_products_cache ()
{
std::cout << "\tContainer () : default constructor.\n";
}
Container (const std::string & filename) : Container ()
{
std::cout << "Container (std::string) : parameters constructor\n";
// Reads from file and store the products in m_products_cache.
// Simulated 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")));
std::cout << "Created container with " << m_products_cache.size () << " product(s).\n";
}
Container (const Container & copy) : m_products_cache (copy.m_products_cache)
{
std::cout << "\tContainer (const Container &) : copy constructor.\n";
}
Product &
CreateNewProduct (const std::string & id, const std::string & name)
{
std::cout << "Creating new product with ID '" << id << "'...\n";
std::map<std::string, Product>::iterator product_it = m_products_cache.find (id);
if (product_it != m_products_cache.end ())
{
std::cout << "Insertion failed, product already exists : " << product_it->second << ".\n";
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)));
std::cout << "Insertion successful. Product located at address " << &inserted_it.first->second
<< " : " << inserted_it.first->second << ".\n";
return inserted_it.first->second;
}
// Case (i.a)
// Returns a reference.
Product &
GetProductById (const std::string & id)
{
std::map<std::string, Product>::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 (i.b)
// 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)
{
std::map<std::string, Product>::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)
{
std::map<std::string, Product>::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)
{
std::cout << " === Create and populate Container ===\n\n";
Container container ("ignored_filename");
std::cout << "\n\n";
std::cout << " === Create and store new Product ===\n\n";
Product & created_product = container.CreateNewProduct ("B-003", "Product 3");
std::cout << " - Created new product : located at " << &created_product << "\n\n";
std::cout << " === Attempt to create and store a duplicated Product ===\n\n";
Product & duplicated_product = container.CreateNewProduct ("B-003", "Product 3"); // Gets product at expected address.
std::cout << " - Duplicated product found : located at " << &duplicated_product
<< (&duplicated_product == &created_product ? " (OK)" : " (Fail)") << "\n\n";
std::cout << " === Obtain reference to Product in container ===\n\n";
// Case (i.a): Returns a reference
Product & product_reference = container.GetProductById ("B-003"); // Gets product at expected address.
std::cout << " - Returned reference : located at " << &product_reference
<< (&product_reference == &created_product ? " (OK)" : " (Fail)") << "\n";
// Case (i.b): Returns a const-reference
const Container & const_container = container;
const Product & product_const_ref = const_container.GetProductById ("B-003"); // Gets product at expected address.
std::cout << " - Returned const reference : located at " << &product_const_ref
<< (&product_const_ref == &created_product ? " (OK)" : " (Fail)") << "\n";
// Case (ii): Output reference parameter
Product product_param; // This calls default constructor.
container.GetProductById ("B-003", product_param); // Question 1: Gets a different copy, but doesn't call copy constructor, WHY?
std::cout << " - Parameter reference : located at " << &product_param
<< (&product_param == &created_product ? " (OK)" : " (Fail)") << "\n";
// Case (iii): Output pointer reference parameter
const Product *product_pointer_param;
container.GetProductById ("B-003", product_pointer_param); // Gets product at expected address.
std::cout << " - Parameter pointer ref. : located at " << product_pointer_param
<< (product_pointer_param == &created_product ? " (OK)" : " (Fail)") << "\n";
}
That produces the following output:
=== Create and populate Container ===
Container () : default constructor.
Container (std::string) : parameters constructor
Product (std::string, std::string) : parameters constructor <A-001, Product 1>.
Product (const Product &) : copy constructor.
Product (const Product &) : copy constructor.
Product (std::string, std::string) : parameters constructor <A-002, Product 2>.
Product (const Product &) : copy constructor.
Product (const Product &) : copy constructor.
Created container with 2 product(s).
=== Create and store new Product ===
Creating new product with ID 'B-003'...
Product (std::string, std::string) : parameters constructor <B-003, Product 3>.
Product (const Product &) : copy constructor.
Product (const Product &) : copy constructor.
Insertion successful. Product located at address 0x6000727b8 : Product <ID 'B-003' : Name 'Product 3'>.
- Created new product : located at 0x6000727b8
=== Attempt to create and store a duplicated Product ===
Creating new product with ID 'B-003'...
Insertion failed, product already exists : Product <ID 'B-003' : Name 'Product 3'>.
- Duplicated product found : located at 0x6000727b8 (OK)
=== Obtain reference to Product in container ===
- Returned reference : located at 0x6000727b8 (OK)
- Returned const reference : located at 0x6000727b8 (OK)
Product () : default constructor.
- Parameter reference : located at 0xffffcaf0 (Fail)
- Parameter pointer ref. : located at 0x6000727b8 (OK)
I have the following questions:
-
Question 1: This code doesn't return a reference to
0x6000727b8and instead creates a copy that is stored at0xffffcaf0, butProduct's copy constructor is not called, why?// Case (ii): Output reference parameter Product product_param; // This calls default constructor. container.GetProductById ("B-003", product_param); // Question 1: Gets a different copy, but doesn't call copy constructor, WHY? -
Question 2: I'd like to provide access to the object in the map, to avoid creating copies of the object. I must also indicate, with a boolean or an exception, if the
Productwas found. I'd prefer something like case (ii):bool GetProductById (const std::string & id, Product & product), but this doesn't enforce constness.Of the three cases I provided in code, or from another suggestion of yours, which is the best practice to achieve it?
-
Question 3: In this case, the
Products stored in the map will not change after they are stored. But, if theProducts were to be changed after they are stored in the map, do approaches like the ones in question 2 still apply?, is it bad practice to change map value objects directly? (I know keys must be constants).
Aucun commentaire:
Enregistrer un commentaire