Problem overview
A ticket machine gives every new ticket the next number: the first ticket gets 1, the next gets 2, and so on. The counter belongs to the class, not to any one ticket. In C++ that is a static data member of Ticket: one copy, shared by all tickets. Each ticket keeps its own number, copied from the counter when the ticket is created.
The class has three parts:
Ticket(name)adds 1 to the class-level counter and gives the new ticket that value as itsnumber. The ticket keeps itsnameandnumberin two fields.- The static method
issued()returns how many tickets were created since the counter was last reset. - The static method
resetCounter()sets the counter back to 0, so the next ticket gets number 1 again.
Creating Ticket("Ann"), Ticket("Bob") and Ticket("Cy") after a reset gives them the numbers 1, 2 and 3, and issued() returns 3. A test case may create up to 1000 tickets, and two tickets can have the same name.
Approach
There are two kinds of data here. The name and the number differ from ticket to ticket, so they are ordinary fields: every object has its own copy. The counter is one value for the whole class, so it is a static member: there is exactly one, whichever tickets exist, and even when none exists.
The constructor does the numbering in two steps: it increments the shared counter, then copies the counter's value into the new ticket's own number. The copy is the important part. The counter keeps moving as more tickets are made, but a ticket's number is a snapshot of the counter at the moment it was created, so it stays the same afterwards. Reading the shared counter later, for example when printing, would give every ticket the latest number.
Both methods and the constructor do a constant amount of work, so creating a ticket costs O(1).
Step by step
- Declare the counter as
static int count;in thepublic:section. One copy serves the whole class. - Declare the per-ticket fields, a
string nameand anint number, withoutstatic, because every ticket needs its own. - Store the name in the constructor with
this->name = name;. The parameter and the field have the same name, sothis->picks the field. - Add 1 to the counter with
count++. Inside the class you can write justcount; outside it, the name isTicket::count. - Copy the counter into the ticket with
this->number = count;. After a reset, the first ticket finds the counter at 1 here, so the numbers start at 1. issued()returnscount. A static method belongs to the class and is called on the class name with no ticket at all, so it can use only static members. That is why it can't usethis.resetCounter()assigns 0 tocount. It runs before each test case, so every case numbers its tickets from 1 again.
The solution in C++
class Ticket {
public:
static int count;
string name;
int number;
Ticket(string name) {
this->name = name;
count++;
this->number = count;
}
static int issued() {
return count;
}
static void resetCounter() {
count = 0;
}
};
int Ticket::count = 0;
The line after the class
The last line, int Ticket::count = 0;, is easy to miss, and the program doesn't link without it. Declaring static int count; inside the class only says that the member exists. The definition, which reserves the memory and gives the starting value, goes once outside the class, with the class name and :: in front. It is already in the lesson's starter code. Leaving it out gives a linker error such as undefined reference to `Ticket::count' as soon as anything uses the counter.
Since C++17 there is another way: a static member declared inline can be given its value inside the class, with no definition outside.
#include <iostream>
#include <string>
using namespace std;
class Pass {
public:
inline static int count = 0;
string name;
int number;
Pass(string name) {
this->name = name;
count++;
this->number = count;
}
};
int main() {
Pass a("Ann");
Pass b("Bob");
cout << a.number << " " << b.number << " " << Pass::count << endl;
}
This prints:
1 2 2
A run
The tests call resetCounter() through the class name before every case, then create the tickets. Here is the first example, followed by what happens to the numbers when the counter moves on:
#include <iostream>
#include <string>
using namespace std;
// the class above goes here
int main() {
Ticket::resetCounter();
Ticket t1("Ann");
Ticket t2("Bob");
Ticket t3("Cy");
cout << t1.name << " #" << t1.number << endl;
cout << t2.name << " #" << t2.number << endl;
cout << t3.name << " #" << t3.number << endl;
cout << Ticket::issued() << endl;
cout << Ticket::count << " " << t1.number << endl;
}
It prints:
Ann #1
Bob #2
Cy #3
3
3 1
The first four lines match the example. The last line shows the difference between the shared counter and a ticket's own number: count is 3, and t1 still has number 1. Reading t1.count works too, because every ticket sees the same shared member, but writing Ticket::count makes it clear that the value belongs to the class.
After a reset the numbers start over, and tickets made earlier keep the numbers they already have:
#include <iostream>
#include <string>
using namespace std;
// the class above goes here
int main() {
Ticket::resetCounter();
Ticket a("Mia");
Ticket b("Mia");
cout << a.number << " " << b.number << " " << Ticket::issued() << endl;
Ticket::resetCounter();
cout << Ticket::issued() << endl;
Ticket c("Mia");
cout << c.number << " " << a.number << " " << Ticket::issued() << endl;
}
It prints:
1 2 2
0
1 1 1
Common mistakes
- Printing or storing
countinstead ofnumber. Every ticket would then show the latest counter value, because the counter is shared. - Declaring
numberasstatic. All tickets would then share one number. - Assigning
numberbefore incrementing the counter, which gives the numbers 0, 1, 2 instead of 1, 2, 3. - Using
thisinsideissued()orresetCounter(). A static method isn't called on an object, so there is nothis, and the compiler rejects it. - Forgetting
resetCounter(): a second case would continue numbering where the first one stopped.
