| 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;
};
#endif
Core*를 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 로 정의하고 싶다면 사용하지 않는 인자를 디폴트 인자로 지정해서 만들면 된다.