AcceleratedC++/Chapter12 | AcceleratedC++/Chapter14 |
~cpp class Core { public: Core(); Core(std::istream&); std::string name() const; std::istream& read(std::istream&); double grade() const; private: std::istream& read_common(std::istream&); std::string n; double midterm, final; std::vector<double> homework; };
~cpp class Grad:public Core { // 구현(implementation)의 일부가 아닌 인터페이스(interface)의 일부로서 상속받는다는 것을 나타냄. public: // Core의 public요소를 그대로 public 요소로 받는다. Grad(); Grad(std::istream&); double grade() const; std::istream& read(std::istream&); private: double thesis; // 논문관련 점수를 저장하는 멤버변수 }Grad 클래스는 Core로 부터 파생되었다(Derived from), 상속받았다(inherits from), 혹은 Core는 Grad의 base class 이다 라는 표현을 사용한다.
~cpp class Core { public: Core(); Core(std::istream&); std::string name() const; double grade() const; std::istream& read(std::istream&); protected: std::istream& read_common(std::istream&); double midterm, final; std::vector<double>homework; private: std::string n;
~cpp string Core::name() const { return n; } double Core::grade() const { return ::grade(midterm. final, homework); } istream& Core::read_common(istream& in) { in>>n>>midterm>>final; return in; } istream& Core::read(istream& in) { read_common(in); read_hw(in, homework); return in; }Grad::read 함수의 오버로딩
~cpp istream& Grad::read(istream& in) { read_common(in); in >> thesis; read_hw(in, homework); return in; }상기의 클래스는 Grad의 멤버 함수로 부모 클래스의 read_common, read_hw의 함수를 그대로 상속받았다는 것을 가정한다.
~cpp istream& Grad::read(istream& in) { Core::read_common(in); in >> thesis; // thesis는 Core가 아니라 Grad의 멤버 변수이므로 범위 지정 연산자를 사용해서는 안된다. Core::read_hw(in, Core::homework); return in; }
~cpp double Grad::grade() const { return min(Core::grade(), thesis); // min()은 <algorithm>에 정의된 함수이다. }Core::grade()를 사용하지 않고 grade()를 사용하게 되면 Grade:grade()를재귀적으로 호출하여 어떤 결과를 리턴할지 예상하지 못한다.
* 전체 객체에 대한 공간을 할당 * 기본 클래스 생성자 호출, 기본클래스 공간 초기화 * 생성자의 초기설정자( ): { 사이에 존재하는 것들 )를 이용해서 파생클래스의 멤버 초기화 * 파생 클래스의 생성자의 본체를 실행한다. |
~cpp class Core { public: Core():midterm(0), final(0) {} Core(std::istream& is) { read(is); } }; class Grad:public Core { public: Grad():thesis(0) {} Grad(std::istream& is) { read(is); } };Grad의 생성자는 Core의 생성자가 midterm, final을 초기화 한다는 가정하에서 thesis만을 초기화하고 있습니다. 이러한 초기화는 암묵적으로 행하여진다.
~cpp bool compare(const Core& c1, const Core& c2) { return c1.name() < c2.name(); }
~cpp Grad g(cin); Core c(cin); compare(g, c); // Grad, Core레코드를 비교한다.Grad 클래스가 사용가능한 이유
~cpp bool compare_grade(const Core& c1, const Core& c2) { return c1.grade() < c2.grade(); }만약 위 함수에 인자로 전달된 객체가 Grad객체라면 그 객체에서 호출되는 grade는 Core::grade() 이어서는 안된다. 그렇게 호출될 경우 논문 점수가 적용되지 않은 성적를 리턴하기 때문이다. 따라서 Grad::grade() 의 함수를 호출해야 할 것이다.
~cpp class Core { public: virtual double grade() const; // virtual 이 추가됨. };virtual 키워드로 지정된 함수는 실제로 함수가 호출될때 그 객체의 이름 범위에 존재하는 함수를 호출하는 것이 가능하다.
~cpp bool compare_grades(Core c1, Core c2) { return c1.grade() < c2.grade(); }상기의 경우 Grad 객체를 인자로 전달할 경우 Grad객체의 Core객체의 요소만 복사되어 함수의 인자로 전달되기 때문에 Core::grade()가 호출되어서 정적바인딩(static binding)이 수행된다.
~cpp #ifndef GUARD_Core_h #define GUARD_Core_h #include <iostream> #include <stdexcept> #include <string> #include <vector> class Core { public: Core(): midterm(0), final(0) { } Core(std::istream& is) { read(is); } std::string name() const; // as defined in 13.1.2/230 virtual std::istream& read(std::istream&); // virtual 키워드로 정의된 함수는 반드시 재정의되어야한다. virtual double grade() const; protected: // accessible to derived classes std::istream& read_common(std::istream&); double midterm, final; std::vector<double> homework; private: // accessible only to `Core' std::string n; }; class Grad: public Core { public: Grad(): thesis(0) { } Grad(std::istream& is) { read(is); } // as defined in 13.1.2/230; Note: `grade' and `read' are `virtual' by inheritance double grade() const; std::istream& read(std::istream&); private: double thesis; }; bool compare(const Core&, const Core&); #endif
~cpp int main() { vector<Core> students; Core record; // Core의 일반형 string::size_type maxlen = 0; while(record.read(cin)) { maxlen = max(maxlen, record.name().size()); students.push_back(record); } sort(students.begin(), students.end(), compare); for (vector<Core>::size_type i = 0; i != students.size(); ++i) { cout<<students[i].name() <<string(maxlen + 1 - students[i].name.size(), ' '); try { double final_grade = students[i].grade(); streamsize prec = cout.precision(); cout<<setprecision(3)<<final_grade <<setprecision(prec)<<endl; } catch (domain_error e) { cout<<e.what()<<endl; } } return 0; }
~cpp int main() { vector<Grad> students; Grad record; // Grad의 일반형 string::size_type maxlen = 0; while(record.read(cin)) { maxlen = max(maxlen, record.name().size()); students.push_back(record); } sort(students.begin(), students.end(), compare); for (vector<Grad>::size_type i = 0; i != students.size(); ++i) { cout<<students[i].name() <<string(maxlen + 1 - students[i].name.size(), ' '); try { double final_grade = students[i].grade(); streamsize prec = cout.precision(); cout<<setprecision(3)<<final_grade <<setprecision(prec)<<endl; } catch (domain_error e) { cout<<e.what()<<endl; } } return 0; }입력을 받는 record가 일반형이기 때문에 위에 2개의 프로그램은 각기 Core, Grad에 대해서 정적으로 바인딩된다.
* 읽어들일 요소들을 저장하는 vector의 정의 * 레코드를 읽어들일 임시 지역 변수의 정의 * read 함수 * grade 함수 |
~cpp int main() { vector<Core*> students; Core* record; while (record->read(cin)) { //... } }위의 프로그램은 할당되지 않은 Core 공간에 값을 대입하려하기 때문에 에러를 발생시킨다. 프로그래머가 객체에 필요한 공간을 직접관리. 읽어들이는 레코드의 종류를 판단해야함.
~cpp bool compare_Core_ptrs(const Core* cp1, const Core* cp2) { return compare(*cp1, *cp2); }인자를 전달하면서 생기는 모호함을 피하기 위해서 compare 라는 이름대신에 compare_Core_ptrs를 사용하여 컴파일러가 명시적으로 이 함수를 사용하도록 한다.
~cpp // main_core.cpp #include <vector> #include <string> #include <algorithm> #include <iomanip> #include <ios> #include <iostream> #include <stdexcept> #include "Core.h" using std::cout; using std::cin; using std::domain_error; using std::endl; using std::setprecision; using std::setw; using std::streamsize; using std::sort; using std::string; using std::vector; using std::max; // this code almost works; see 13.3.2/242 int main() { vector<Core*> students; // store pointers, not objects Core* record; // temporary must be a pointer as well char ch; string::size_type maxlen = 0; // read and store the data while (cin >> ch) { if (ch == 'U') record = new Core; // allocate a `Core' object else record = new Grad; // allocate a `Grad' object record->read(cin); // `virtual' call maxlen = max(maxlen, record->name().size());// dereference students.push_back(record); } // pass the version of `compare' that works on pointers sort(students.begin(), students.end(), compare_Core_ptrs); // write the names and grades for (vector<Core*>::size_type i = 0; i != students.size(); ++i) { // `students[i]' is a pointer that we dereference to call the functions cout << students[i]->name() << string(maxlen + 1 - students[i]->name().size(), ' '); try { double final_grade = students[i]->grade(); streamsize prec = cout.precision(); cout << setprecision(3) << final_grade << setprecision(prec) << endl; } catch (domain_error e) { cout << e.what() << endl; } delete students[i]; // free the object allocated when reading } return 0; }입력과 출력의 각 부분에서 그리고 컨테이너의 요소를 사용할때 포인터를 이용함으로해서 프로그램이 동적바인딩을 이용해서 상당히 간결해진 것을 확인할 수 있다.
~cpp class Core { public: virtual ~Core() { } //이전과 동일 };상기와 같이 빈 소멸자를 사용하는 것은 흔한 경우이다. 기본 타입의 소멸자를 virtual 로 만듦으로서 파생 클래스에서 발생하는 기타 요소들을 해제해야할 경우가 많기 때문이다.
~cpp //Student_info.h #ifndef GUARD_Student_info_h #define GUARD_Student_info_h #include <iostream> #include <stdexcept> #include <string> #include <vector> #include "Core.h" class Student_info { public: // constructors and copy control Student_info(): cp(0) { } Student_info(std::istream& is): cp(0) { read(is); } Student_info(const Student_info&); Student_info& operator=(const Student_info&); ~Student_info() { delete cp; } // operations std::istream& read(std::istream&); std::string name() const { if (cp) return cp->name(); else throw std::runtime_error("uninitialized Student"); } double grade() const { if (cp) return cp->grade(); else throw std::runtime_error("uninitialized Student"); } static bool compare(const Student_info& s1, const Student_info& s2) { // 정적 멤버 함수의 선언 return s1.name() < s2.name(); } private: Core* cp; }; #endifCore*를 Student_info를 통해서 Wrapping 시킴으로써 프로그래머는 실제로르 Student_info가 2가지 종류의 객체를 다룰 수 있지만 실제의 내부 구현은 알지 못하게 된다.
~cpp istream& Student_info::read(istream& is) { delete cp; // 언어적으로 널포인터를 해제하는 것은 문제가 없으므로 체크하는 코드를 넣지 않아도 된다. char ch; is>>ch; if(ch == 'U') { cp = new Core(is); } else { cp = new Grad(is); } return is; }우선은 첫번째 인자를 받아서 생성할 객체의 타입을 결정하고, 결정된 타입의 객체를 생성시킨다.
~cpp class Core { friend class Student_info; protected: virtual Core* clone() const { return new Core(*this); } }; class Grad { protected: Grad* clone() const { return new Grad(*this); } // 본래 virtual 함수에서는 기본클래스와 파생클래스에서 // 오버로드한느 함수의 파라메터 명세, 리턴형이 동일해야하지만 // 포인터형인 경우에는 파생 클래스의 포인터를 사용하는 것이 가능하다. };friend class Student_info; 를 사용함으로해서 Student_info의 모든 멤버함수는 Core의 protected, private에 접근하는 것이 가능하다.
~cpp Student_info::Student_info(const Student_info& s) : cp(0) { if (s.cp) cp = s.cp->clone(); } Student_info& Student_info::operator=(const Student_info& s) { if (&s != this) { delete cp; if (s.cp) cp = s.cp->clone(); else cp = 0; { return *this; }
~cpp //main_perfect.cpp #include <algorithm> #include <iomanip> #include <ios> #include <iostream> #include <stdexcept> #include <string> #include <vector> #include "Student_info.h" using std::cin; using std::cout; using std::domain_error; using std::endl; using std::setprecision; using std::setw; using std::sort; using std::streamsize; using std::string; using std::vector; using std::max; int main() { vector<Student_info> students; Student_info record; string::size_type maxlen = 0; // read and store the data while (record.read(cin)) { maxlen = max(maxlen, record.name().size()); students.push_back(record); } // alphabetize the student records sort(students.begin(), students.end(), Student_info::compare); // write the names and grades for (vector<Student_info>::size_type i = 0; i != students.size(); ++i) { cout << students[i].name() << string(maxlen + 1 - students[i].name().size(), ' '); try { double final_grade = students[i].grade(); streamsize prec = cout.precision(); cout << setprecision(3) << final_grade << setprecision(prec) << endl; } catch (domain_error e) { cout << e.what() << endl; } } return 0; }
~cpp vector<Core> students; Grad g(cin); students.push_back(g);유효한 표현이기는 하지만 g의 Core클래스에서 정의된 부분 만이 저장이 된다.
~cpp //만약 r이 Core의 객체이고 Core::regrade(double)는 인자로 받은 것을 final에 기록한다. //Grad::regrade(double, double) 인자로 받은값으로 final, thesis를 할당 r.regrade(100); // 작동 r.regrade(100, 100); // 컴파일 오류
~cpp //만약 r이 Grad의 객체이고 Core::regrade(double)는 인자로 받은 것을 final에 기록한다. //Grad::regrade(double, double) 인자로 받은값으로 final, thesis를 할당 r.regrade(100); // 컴파일 오류. Grad::compare 를 기대하기 때문에 이런 문제가 발생한다. r.Core::regrade(100); // 작동. 범위 연산자를 이용해서 명시적으로 호출하는 것은 허용한다. r.regrade(100, 100); // 작동.
~cpp virtual void Core::regrade(double d, double =0) { final = d; }만약 이런 함수를 virtual 로 정의하고 싶다면 사용하지 않는 인자를 디폴트 인자로 지정해서 만들면 된다.