Overview
The C++ standard library provides a powerful abstraction through streams. The std::ostream class serves as a base for both console output (std::cout) and file output (std::ofstream). Similarly, std::istream is the base for std::cin and std::ifstream. This polymorphic relationship allows functions accepting stream references to work with any derived stream type without modification.
Task 1: ACM Contestant Ranking System
Data Structure Definition
// contestant.hpp
#pragma once
#include <iomanip>
#include <iostream>
#include <string>
struct Contestant {
long id;
std::string name;
std::string department;
int solved;
int penalty;
};
inline std::ostream& operator<<(std::ostream& out, const Contestant& c) {
out << std::left;
out << std::setw(15) << c.id
<< std::setw(15) << c.name
<< std::setw(15) << c.department
<< std::setw(10) << c.solved
<< std::setw(10) << c.penalty;
return out;
}
inline std::istream& operator>>(std::istream& in, Contestant& c) {
in >> c.id >> c.name >> c.department >> c.solved >> c.penalty;
return in;
}
Utility Functions
// utils.hpp
#pragma once
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include "contestant.hpp"
// ACM ranking: descending by problems solved, ascending by penalty
inline bool rank_contestants(const Contestant& a, const Contestant& b) {
if (a.solved != b.solved)
return a.solved > b.solved;
return a.penalty < b.penalty;
}
inline void output_results(std::ostream& os, const std::vector<Contestant>& data) {
for (const auto& entry : data)
os << entry << '\n';
}
inline void display_results(const std::vector<Contestant>& data) {
output_results(std::cout, data);
}
inline void write_file(const std::string& filename, const std::vector<Contestant>& data) {
std::ofstream os(filename);
if (!os)
throw std::runtime_error("cannot open " + filename);
output_results(os, data);
}
inline std::vector<Contestant> read_file(const std::string& filename) {
std::ifstream is(filename);
if (!is)
throw std::runtime_error("cannot open " + filename);
std::string header;
std::getline(is, header); // skip header line
std::vector<Contestant> data;
Contestant record;
int index;
while (is >> index >> record)
data.push_back(record);
return data;
}
Main Application
// main.cpp
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <vector>
#include "contestant.hpp"
#include "utils.hpp"
const std::string input_file = "./data.txt";
const std::string output_file = "./ans.txt";
void run() {
std::vector<Contestant> participants;
try {
participants = read_file(input_file);
std::sort(participants.begin(), participants.end(), rank_contestants);
display_results(participants);
write_file(output_file, participants);
} catch (const std::exception& e) {
std::cerr << e.what() << '\n';
}
}
int main() {
run();
}
Discussion
Question 1: Polymorphic Stream Usage
std::ostream acts as a common base class for both std::cout (screen output) and std::ofstream (file output). By accepting a reference to the base class, the output_results() funtcion can work with either stream type. This design enables code reuse without modification.
Question 2: Exception Handling
When read_file() attempts to open a non-existent file, it throws a std::runtime_error. The calling code wraps the operation in a try-catch block that catches std::exception (the base class for all standard exceptions). The what() method retrieves the error message, and return terminates the function cleanly.
Question 3: Function Replacement
The display_results() function could be replaced with a direct call to output_results(std::cout, data) since it merely wraps that functionality. The behavior remains identical.
Task 2: Student Management System
Class Declaration
// student.hpp
#pragma once
#include <iostream>
#include <string>
class Student {
public:
Student() = default;
~Student() = default;
std::string get_major() const;
int get_score() const;
friend std::ostream& operator<<(std::ostream& os, const Student& s);
friend std::istream& operator>>(std::istream& is, Student& s);
private:
int id;
std::string name;
std::string major;
int score;
};
Manager Class Declaration
// stumgr.hpp
#pragma once
#include <string>
#include <vector>
#include "student.hpp"
class StuMgr {
public:
void load(const std::string& file);
void sort_records();
void display() const;
void persist(const std::string& file) const;
private:
void write(std::ostream& os) const;
std::vector<Student> records;
};
Class Implementation
// student.cpp
#include "student.hpp"
std::string Student::get_major() const {
return major;
}
int Student::get_score() const {
return score;
}
std::ostream& operator<<(std::ostream& os, const Student& s) {
os << s.id << ' ' << s.name << ' ' << s.major << ' ' << s.score;
return os;
}
std::istream& operator>>(std::istream& is, Student& s) {
is >> s.id >> s.name >> s.major >> s.score;
return is;
}
// stumgr.cpp
#include "stumgr.hpp"
#include <fstream>
#include <stdexcept>
#include <algorithm>
void StuMgr::load(const std::string& file) {
std::ifstream fs(file);
if (!fs.is_open())
throw std::runtime_error("cannot open file: " + file);
records.clear();
Student s;
std::string header;
std::getline(fs, header);
while (fs >> s)
records.push_back(s);
fs.close();
}
void StuMgr::sort_records() {
std::sort(records.begin(), records.end(),
[](const Student& a, const Student& b) {
if (a.get_major() != b.get_major())
return a.get_major() < b.get_major();
return a.get_score() > b.get_score();
});
}
void StuMgr::display() const {
write(std::cout);
}
void StuMgr::persist(const std::string& file) const {
std::ofstream fs(file);
if (!fs.is_open())
throw std::runtime_error("cannot open file: " + file);
write(fs);
fs.close();
}
void StuMgr::write(std::ostream& os) const {
for (const auto& s : records)
os << s << '\n';
}
Main Application
// task2.cpp
#include <iostream>
#include "stumgr.hpp"
const std::string input_file = "./data.txt";
const std::string output_file = "./ans.txt";
void show_menu() {
std::cout << "\n========== Menu ==========\n"
"1. Load file\n"
"2. Sort\n"
"3. Display\n"
"4. Save to file\n"
"5. Exit\n"
"Select: ";
}
void run() {
StuMgr manager;
while (true) {
show_menu();
int choice;
std::cin >> choice;
try {
switch (choice) {
case 1:
manager.load(input_file);
std::cout << "Load succeeded\n";
break;
case 2:
manager.sort_records();
std::cout << "Sort completed\n";
break;
case 3:
manager.display();
std::cout << "Display completed\n";
break;
case 4:
manager.persist(output_file);
std::cout << "Save succeeded\n";
break;
case 5:
return;
default:
std::cout << "Invalid choice\n";
}
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << '\n';
}
}
}
int main() {
run();
}
Key Concepts Demonstrated
-
Stream Polymorphism: Both implementations use base class references (
std::ostream&,std::istream&) to achieve code reuse across console and file operations. -
Exception Safety: File operations throw
std::runtime_errorwhen fialures occur, and calling code handles these exceptions gracefully with try-catch blocks. -
Algorithm Integration:
std::sortwith custom comparators handles multi-criteria sorting (ACM ranking and student sorting by major/score).