So in this main.cpp, I have to implement a class’s interface where it reads a groceryitem from standard input until end of file. I am to do this is by storing the object in a dynamically created object, storing the grocery item’s pointer in a standard vector, and trying to prompt for data within the loop’s condition clause. Here is the following code.
#include <iomanip>
#include <iostream>
#include <utility>
#include <vector>
#include "GroceryItem.hpp"
int main() {
std::cout << "Welcome to Kroger. Place grocery items into your shopping cart by entering each item's information. n"
<< " enclose string entries in quotes, separate fields with comas n"
<< " for example: "00016000306707", "Betty Crocker", "Double Chocolate Chunk Cookie Mix", 17.19 n"
<< " Enter CTL-Z (Windows) or CTL-D (Linux) to quit n";
std::vector<GroceryItem *> shoppingcart;
do {
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen";
GroceryItem* Item = new GroceryItem;
std::cin >> *Item;
shoppingcart.push_back( Item );
std::cout << "Item added to shopping cart: " << *shoppingcart.back() << "n";
} while( !( std::getline( std::cin, *Item ) ) );
std::cout
<< "Here is an itemized list of the items in your shopping cart:n";
for (auto i = shoppingcart.begin(); i < shoppingcart.end(); i++) {
std::cout << **i << 'n';
}
for (const auto erase : shoppingcart ) {
delete erase;
}
return 0;
}
Aside from the do while statement, everything else does it’s job as needed. Yet, for the while loop, although the pattern is meant to be a loop-and-a-half pattern, i’m not allowed to use any decision statements within the body of the loop (ie, if’s or break’s). Furthermore, i’m not allowed to include fstream or call the ifstream::open function, but I can create a text file with my input and then redirect input from that text file (the only problem is that I don’t know how, considering that every solution I have found uses fstream). Does anybody have an idea of what I could write for this while loop? The current do-while above will not work because of the error below.
./main.cpp:21:40: error: use of undeclared identifier 'Item' [Semantic Issue]
21 | } while( !( std::getline( std::cin, *Item ) ) );
|
Also, here is the sample input:
"00072250018548","Nature's Own","Nature's Own Butter Buns Hotdog - 8 Ct",10.79
"00028000517205", "Nestle" ,
"Nestle Media Crema Table Cream" ,
17.97
"00034000020706" ,
"York",
"York Peppermint Patties Dark Chocolate Covered Snack Size",
12.64 "00038000570742",
"Kellogg's", "Kellogg's Cereal Krave Chocolate",
18.66
"00014100072331" , "Pepperidge Farm", "Pepperidge Farm Classic Cookie Favorites", 14.43
"00000000000000", "incomplete / invalid item"
Thank you.
Cevapi Man69 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
5
Avoid owning pointers, new and delete
Unless the problem specification requires it, you should avoid the use of: (1) owning raw pointers, (2) operator new
, and (3) operator delete
. By owning, I mean a pointer for which operator delete
must be called later.
If, for instance, you are required to use pointers to dynamically allocated GroceryItem
s, you should use a smart pointer, such as std::unique_ptr
.
std::vector<std::unique_ptr<GroceryItem>> shoppingcart;
If, however, the problem specification for your shoppingcart
does not explicitly require the use of pointers, then you will be better off just declaring a vector of GroceryItem
objects.
std::vector<GroceryItem> shoppingcart;
A loop-and-a-half
This is a bit of a niggle, but without some sort of break-statement or return-statement that causes an exit from within the body of a loop, then, technically, the loop is not a loop-and-a-half.
So, if you cannot use break
, then you must use a “normal” loop to simulate the behavior of a “loop-and-a-half.” The way to do that is to repeat half of the loop body right before (or, in some cases, right after) the loop. The repeated code is the “half” part in “loop-and-a-half.”
For the program in the OP, the repeated code goes right before the loop.
std::vector<GroceryItem> shoppingcart;
GroceryItem item;
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen"; // REPEATED
while (std::cin >> item) {
shoppingcart.emplace_back(std::move(item));
std::cout << "nItem added to shopping cart: n" << shoppingcart.back() << "n";
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen"; // REPEATED
}
Here is the same code, but this time using a break-statement. A “normal” loop has the loop-termination test at either the top or bottom of the loop. A “loop-and-a-half” has it in the middle. Either way, the loop has a single entry, and single exit.
std::vector<GroceryItem> shoppingcart;
GroceryItem item;
for (;;) {
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen";
if (!(std::cin >> item))
break;
shoppingcart.emplace_back(std::move(item));
std::cout << "nItem added to shopping cart: n" << shoppingcart.back() << "n";
}
They told me I gotta use pointers!
The code does not look much different when you use smart pointers and dynamically allocated GroceryItem
s. In part, that’s because you can dereference a smart pointer using the same *
operator you use for regular pointers.
std::vector<std::unique_ptr<GroceryItem>> shoppingcart;
GroceryItem item;
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen";
while (std::cin >> item) {
shoppingcart.emplace_back(std::make_unique<GroceryItem>(std::move(item)));
std::cout << "nItem added to shopping cart: n" << *shoppingcart.back() << "n";
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen";
}
Notes:
- The program uses
operator>>
to extract aGroceryItem
, and store it in “local” variableitem
. - The
item
is then converted into an rvalue (bystd::move
). This ravlue becomes the argument tostd::make_unique
. std::make_unique
allocates aGroceryItem
on the heap, and returns astd::unique_ptr
that points to it.- Because the argument to
std::make_unique
is an rvaluestd::make_unique
will use the move constructor from classGroceryItem
when it initializes theGroceryItem
it has allocated on the heap. This requires, of course, that a move constructor exists. If not,std::make_unique
will use the copy constructor.
There is a big advantage in using smart pointers. You never have to call operator delete
on them. The destructor of a smart pointer does that for you.
My boss insists: it’s gotta be raw pointers!
It is not as easy as you might think to adapt this for use with raw pointers. The following may be what a teacher expects on a homework assignment, but it is buggy code.
// WARNING: This leaks memory!
std::vector<GroceryItem*> shoppingcart; // "raw" pointers
GroceryItem item;
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen";
while (std::cin >> item) {
shoppingcart.emplace_back(new GroceryItem{ std::move(item) }); // may throw
std::cout << "nItem added to shopping cart: n" << *shoppingcart.back() << "n";
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen";
}
// More code ...
// Delete pointers
for (auto& p : shoppingcart) {
delete p;
p = nullptr;
}
This may look okay, because there is a loop at the end that deletes all the pointers.
It’s buggy, however, because it has the potential to leak memory. That will happen when the vector is not empty, and a call to operator new
throws a std::bad_alloc
. It will also happen when the vector is not empty, and an exception of any kind is thrown.
In order to prevent these sorts of memory leaks, you have to build an RAII class around the vector—the class might be named ShoppingCart
—and move the deletion loop into the destructor for that class. The destructor is guaranteed to be called, even when an exception is thrown, so no matter what happens, all the pointers will be deleted.
Stream extraction operator
These examples use the stream extraction operator>>
that was developed for your last question. It follows the example from the answer I posted there.
std::vector<GroceryItem *> shoppingcart;
You should store things by-value unless there’s a good reason not to, so change the line above to:
std::vector<GroceryItem> shoppingcart;
i’m not allowed to use any decision statements within the body of the loop (ie, if’s or break’s).
You want the success of parsing a GroceryItem
to control the loop though, so should move the parsing attempt down to the while
condition:
do {
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen";
shoppingcart.emplace_back(); // add a default-constructed GroceryItem
} while (std::cin >> shoppingcart.back() &&
std::cout << "Item added to shopping cart: "
<< shoppingcart.back() << 'n');
shoppingcart.pop_back(); // remove last one we didn't parse into successfully
The above is a bit scrappy, but if the GroceryItem
class implements operator>> properly for parsing from streams, then we need a GroceryItem
ready to overwrite with the items successively read from input. We could add a default-constructed GroceryItem
to the vector
each time, and overwrite it in place, which I’ve illustrated above. Note the use of the short-circuit logical-AND operator &&
in the while
condition: that means execution only invokes the std::cout << ...
part if the std::cin >>
part indicates success.
An alternative is to create one before the loop and keep blatting over that:
GroceryItem gi;
do {
std::cout << "Enter UPC, Product Brand, Product Name, and Pricen";
} while (std::cin >> gi &&
(shoppingcart.push_back(std::move(gi)),
std::cout << "Item added to shopping cart: "
<< shoppingcart.back() << 'n'));
Here, we only push_back
if std::cin >> gi
succeeds, but because push_back
doesn’t return anything to indicate success, we ignore its return value and add the std::cout statements return value to the condition using the comma operator: basically, (first_expression, second_expression)
runs first_expression
, then second_expression
, but only the latter’s return value is substituted into the containing expression.
To invoke the program on a file, on Linux you can use either of:
./my_program < my_input_file
cat my_input_file | ./my_program
On Windows, try:
.my_program < my_input_file
NOTE: for the above to work, GroceryItem must have a working operator>> that copes properly with the quoted strings, comma separators, newline delimiters, and the shorter invalid-grocery-item input. Writing that is quite fiddly for beginners. Hopefully your teacher’s provided something already. If not, you’ll want to use code such as is >> std::quoted(groceryitem.upc) >> sep && sep == ',' && ...
to read the quoted strings, then after reading two parts check for the invalid item and decide whether to continue reading the product name and price.