Chapter 10. Objects and Classes

지환·2022년 7월 5일
0

Procedural Programming : 말 그대로 procedural을 먼저 생각한다. 어떤 절차로 진행할지,, 후에 데이터를 어떻게 표현할지 생각
Object-Oriented Programming : data에 대해 먼저 생각. data를 어떻게 표현하고 어떻게 사용할지,,

(p.513)
OOP는 결국 programming style이기 때문에 어떤 언어로도 구현할 수 있다.
knk에서 봤듯이 C로도 ADT 같은걸 구현해낼 수 있다. 하지만 C++이 더 최적화돼있다.
예를들어, C에선 한 file당 한 type을 표현하지만(data랑 그 data에 function 다 구현),
C++에선 class 단위로 type을 정의할 수 있어서 한 file에 여러 type을 정의할 수 있다.
또 private를 통해 보안이 더 강화된다. C에선 직접적인 접근을 완벽하게 막을 순 없다.
(incomplete type으로 어떻게 되긴 되는데 어쨌든..)

Abstraction and Classes

복잡함을 다루는 방법 중 하나가 abstraction이다. C++에선 class를 이용해 abstraction을 구현한다.

What Is a Type?

1) data object를 저장하는데 얼만큼의 memory가 필요한지
2) 해당 data가 저장된 memory의 bits를 어떻게 해석할지
3) 어떤 operation이 가능한지
위 세 요소가 Type을 결정한다. user-defined type을 만들더라도 이걸 다 구현해내야 한다.

Classes in C++

Class Specification : 아래 두가지로 구현된다.
1. class declaration(definition) : data members, member functions(methods)
2. class method defintions : class member functions의 definitions
보통은 기존 함수에 했듯이, 1번은 header에 만들고, 2번은 다른 source file에 만든다.

Interface란?
상호작용하는 두 system사이에 공유하는 framework(틀)이다.
예를들어 computer program과 user 사이에도 그런게 있는데, wordprocessor가 있다.
우리는 모든 것들을 바로 컴퓨터 메모리에 작성하는게 아니라 word processor라는 interface를 통한다.
class에도 마찬가지로 이런 개념이 있다.(header를 interface로 한거랑 같은 맥락이네)
class design은 public user로부터의 직접 접근을 거부한다.

1. class declaration

// stock00.h
#ifndef STOCK00_H_
#define STOCK00_H_

#include <string>

class Stock {
private:
	std::string company;  //data도 오고
    long shares;
    double share_val;
    double total_val;
    void set_tot() { total_val = shares * share_val; }  //이렇게 definition이 와도 되고
public:
	void acquire(const std::string & co, long n, double pr);
    void buy(long num, double price);  //아니면 이렇게 prototype 와도 되고
    void sell(long num, double price);
    void update(double price);
    void show();
};  //semicolon at the end

#endif

//istream class에도 getline 같은 함수 prototype이 이렇게 class declaration에 있을 거임

class 를 앞에 붙임으로써 class라는 것을 알려준다.
structure 선언이랑 비슷한데, private/public으로 나뉘는거랑 함수가 있는 점이 다르다.
이 declaration을 통해 object(instance)라 불리는 variable을 선언할 수 있다.
ex) Stock sally;

Access Control

  • public
    : 어떤 곳이든 해당 object만 이용하면 public 부분에 직접 접근할 수 있다.
  • private
    : 오직 public member function을 통해서만 접근할 수 있다.(혹은 friend function(Ch11))
    이것을 data hiding이라 하는데, data의 integrity를 보장하기때문에 좋은 practice이다.
    data hiding의 장점은 (1)data 보호도 있지만, (2)detail을 알 필요가 없어진다는 것도 있다.
    interface대로 사용법만 익히면 될 뿐, 자세한 작동 원리는 몰라도 된다.
    그래서 나중에 data type을 변경한다던가, algorithm을 변경해야 하더라도 쉽게 할 수 있는 것이다. 유지 보수가 간단해진다.
  • protected
    : Ch13

class의 default access control은 private이므로 굳이 명시해줄 필요는 없다.(그래도 해주는게 좋긴하겠지)

Class는 주로 public interface과 implementation specific을 분리되도록 design한다.
(뭐 함수 다룰때처럼 같은 file에 몰아도 되지만, 그러면 유지보수나 재사용성이 떨어진다.)
이러한 것을 encapsulation이라고 한다.
ex) private section에 넣는 data hiding이나, class declaration과 class function definition을 분리하는 것 모두 encapsulation이다.

Classes & Structures
: access control mode가 있단 것과 함수가 올 수 있단걸 제외하면 둘은 거의 같다.
실제로 C++에선 structure를 class 수준으로 확장한다.
구조체 안에서 함수도 만들 수 있고, private 같은 것도 작성할 수 있다.
"유일한 차이"는 structure의 default access type이 public이란 것이다.
(p.890)structure는 public이 default인 class이다.

하지만 class는 그 목적대로 사용하고,
★structure도 pure data object를 나타낼 때만, 즉 원래 사용하던대로 그냥 사용한다.★
(plain-old data structures라고 함. POD structures)

2. class method definitions

class declaration 함수들의 definitions을 정의해 주는 두번째 단계.
여기서 class declaration이 있는 header를 include 해야한다. class도 scope이긴하지만, namespace와는 좀 달라서 class declaration이 보이지 않으면 그 member 함수를 정의할 수 없음.

1) 어떤 class에 속한 함수인지 구별하기위해 scope-resolution operator(::)를 사용해서 정의
void Stock::update(double price) { ~ }
class도 class scope를 가지므로 저 연산자를 사용하는 것이다.
그렇다고 namespace에서처럼 저것만 사용하면 다 접근가능한 건 아니고, 이렇게 함수 정의할때 지정해주는 정도 (일반 namespace와 달리 특별한 처리가 있겠지?->ㅇㅇ this 때문에 object있어야지)
다른 member 함수 내에서는 unqualified name으로 접근 가능(같은 class scope니까 <- p.523)

2) Class methods는 class의 private component에 접근 가능

나머지는 일반 함수 정의와 같다.(return type, parameter 적어줘야되고..)

class는 결국 C에서 만들어봤던 interface와implementation을 묶어서 전문화 해주는 셈이다.
그렇기때문에 유사점(class 자체적으로 scope가진다)도 있지만,
public/private 같은 개념은 C로 구현?하긴 쉽지않은 개념이다.(좀생각해봐도 완벽하게는 잘 안될거같음)
private 함수는 그냥 implementation에서 static으로 만들면되고,
변수를 private하게 하고싶으면 incomplete type 사용하는 것이고,
public은 따로 implementation에 global 변수 만들면 된다.
protected는 좀 어렵네..
쨌든 이런 유사성도 있지만, C++은 class로 나름대로 전문화시킨거니까
이 개념은 이 개념대로 받아들이고 비슷한 점만 한번씩 짚어보자.

Inline Methods
class declaration내의 함수 definitions은 inline function이 된다.
(맞네 역시 class declaration(interface)에선 일반적인 정의 같은게 안오는게 맞지. c++에서 inline 함수는 특별한 놈이니까(까먹었으면 앞에 다시 보고오삼))

class declaration 내에 inline 함수 definition 작성할시 rewrite rule 에 의해 그 자리는 prototype으로 대체되고,
class declaration 바로 뒤에 inline function definition이 따라오게 된다.

class Stock {
private:
    . . .
    void set_tot();
    . . .
};
inline void Stock::set_tot() {
	total_val = shares * share_val;
}

즉 제일 위 inline 함수는 이렇게 적힌다. 다시말해 inline 함수를 굳이 class 선언안쪽이 아니라 이렇게 바깥에 작성할 수도 있다.

Object 사용

Stock kate, joe; : class를 이용한 object 선언. 각 object는 그것만의 공간을 할당받는다.
Stock *kate = new Stock; : 이렇게 포인터로도 object를 만들 수 있다.
kate.show(); : kate object가 멤버함수를 호출한 것

내부 data는 object마다 각각 한 set을 가지지만, 함수는 한 set을 여러 object가 사용한다.
함수는 모든 object가 같은 함수를 사용하지만, 함수에 넘겨주는 정보가 다른 것이다.
(이거는 뭐 C로도 구현되는 그 개념대로네)

다른 buit-in type처럼 함수 paramter, return type에도 올 수 있고, assignment도 가능하다.
보통 class를 이용해 원하는 type을 만들고, main에서 그걸 사용해 프로그램 만드는 식.

member 함수가 뭘 하는지 정도만 알면 된다.
애초에 implementation detail을 몰라도 (즉 그런 사항을 활용하지 않고) 실행되도록 설계했다.
Cilent/Server도 마찬가지이다. Client의 의무는 그냥 interface만 잘 보고 사용하면 되는 것이다.
Server의 의무는 내부적으로 어떻게 정확하게 좋은 performance를 낼지 구현하는 것이다.
따라서 둘은 독립적으로 개발될 수 있는 것

>Output Format 유지
멤버함수에서 cout 같은거 사용할때 output format(소숫점자리 등)을 바꿀 수도 있다.
다른 곳에 영향을 미쳐선 안되므로 (C에서처럼 순서가 명확하게 보이는게 아니라 원상복구 해주는게 좋지. C에서도 이런건 원상복구 해주는게 좋고), 아래와 같이 기존 정보 저장했다가 복구해주면 된다.

std::streamsize prec = std::cout.precision(3);
std::ios_base::fmtflags orig = std::cout.setf(std::ios_base::fixed);
//바로 위 코드 p.521이랑 p.522 코드가 좀 다르다. 나중에 자세히 배우고 다시 보자.
. . .
std::cout.precision(prec);
std::cout.setf(orig, std::ios::floatfield);

정리

class declarartion을 structure declaration을 모델로 한다.
보통private에 data memeber declaration이 오고, public에 member function prototype이 온다.
data member를 숨기는게 이상적이다. object로 막 접근해서 정제된 조작없이 임의로 수정가능하면 문제가 됨.
(knk에서도 이렇게 만들었었음. 이런 일반적인 경우는 C로 구현이 되겠는데? C++ class는 좀 특수한 경우도 허용해서 확장한거네그냥, 더 알아보고 만들기 쉽게하고 그정도네)


Class Constructors / Destructors

About class Constructors

C++에선 class object가 일반적인 type들과 비슷하게 사용되도록 한다.
initializing도 한 예인데, class object는 일반적인 initializer가 안 통할 수도 있다.
private section 때문에 그런 것이다. 얘는 member 함수를 통해서"만" 접근할 수 있기 때문에 그렇다.
initializing되는 모든 data가 public이라면 Stock abb = {"adk", 200, 50.1}; 식의 initialization도 작동한다. 하지만 class 주 목적이 data hiding이라서 이렇게는 잘 안한다.
(static이 있으면 저런 형식의 초기화는 compile error)
그래서 우리가 따로 (1)초기화해주는 member function을 만들어야한다. member function으로만 private 변수에 접근할 수 있으니까..
근데 이것도 문제인게, 그런 초기화 함수를 사용하도록 강제할 수가 없다. 초기화 먼저 해야되는데 안하고 그냥 다른 함수 써버릴 수도 있단 뜻.
이를 해결하기위해 (2)object만들때 자동으로 initialize 되도록 하는 방법이 있다.
C++에선 class constructor라는 특별한 멤버 함수를 통해 이를 할 수 있도록 한다.

Default Constructors

즉 우리는 Strock abb;라고 호출했을때 바로 abb를 자동으로 초기화하는 함수를 원하는 것이다.
이를 default constructors라고 하는데, 우리가 이 default constructor를 따로 정의해주지 않는다면, C++이 자동으로 아무것도 하지않는 함수를 추가해서 사용한다. 아무것도 하지 않기때문에 기존처럼 딱히 특정한 값으로 초기화 안됨.(예를들어 int a; 처럼 되는 것)
우리가 원래 아무렇지 않게 쓰던 class object 선언에도 사실은 constructor가 쓰였단 소리이다.

따로 정의 하고 싶다면 아래 방식으로 constructor를 정의한다.
(참고로 default constructor는 하나밖에 못온다.)

  1. return type이 없다.
  2. 함수 이름은 class 이름과 같다.
  3. prototype은 class declaration의 public에 놓는다.(private이면 다른데서 접근 불가니 또 문제)

+) 추가로 default constructor(1)argument를 받지않거나, (2)default argument를 이용해 정의한다.

(1)의 경우만 일단 보자.
class declaration의 public 파트에 prototype 추가 후, 아래는 definition

Stock::Stock() {
	company = "no name";
    shares = 0;
    share_val = 0.0;
    total_val = 0.0;
}

>사용
아래와 같이 default constructor를 호출할 수 있다.
Stock first; : 알다시피 자동으로 호출됨. implicitly
Stock first = Stock(); : Stock 함수를 직접 호출한다. explicitly
Stock *prelief = new Stock; : 이 경우도 자동으로 호출된다. implicitly
(두번째 경우는 뭐 함수가 반환값도 없고 하니까 특수한 경우로 알아서 잘 정의돼있겠지..?)

Stock 함수 호출하려면 object 통해야되는거 아닌가?
constructor가 먼저 개입해야 object가 생성되는거임. 그러기때문에 얘는 예외적으로 이렇게 된다.
(p.549)그리고 얘는 이름 자체가 class랑 같아서 이름만으로 다른 것과 구분이 됨.(인지됨.)

마지막 부분 문법 좀 수정해서 'Stock p=new Stock();'도 가능하다.
new엔 type이 오는데 함수가 어떻게 함수가 오지?
stackoverflow.com/questions/2941888/how-operator-new-calls-the-constructor-of-class
이 링크에 있듯이 compiler가 잘 해석해주는 듯

Parameterized Constructors

default constructor를 확장해서, C++에선 우리가 직접 초기 값을 넘겨줄 수 있도록 한다.
constructor 정의 방식
1. return type이 없다.
2. 함수 이름은 class 이름과 같다.
3. prototype은 class declaration의 public에 놓는다.
이렇게 위와 같고, 대신 argument를 원하는대로 받는 것이다.(overloading 가능)

예시)
Stock(const string& co, long n, double pr = 0.0); : prototype in public
Stock::Stock(const strign & co, long n, double pr) { ~ } : definition
이렇게 정의해두면 object만들어질때 program이 자동으로 이 함수를 호출한다.
그리고 우리가 넘겨준 값을 이용해 member 변수를 초기화한다.
위처럼 default argument를 이용해도 되는데, 모든 parameter가 default argument이면 이는 default constructor이다.

>사용
Stock garment("Furry Mason", 50, 2.5); : implicitly call
Stock food = Stock("World Cabbage", 250, 1.25); : explicit call
Stock *pstock = new Stock("Elec Game", 18, 15.6);

매개변수를 받을때 class member 변수와 같은 이름은 당연히 사용하면 안된다.
당연히 그렇게 한다고 자동으로 assign되는게 아님.
그래서 보통, class 멤버 변수는 `m_` prefix를 가지던가 `_` postfix를 가지도록 하는 관습이 있다.

첫번째 문법은 built-in type에서도 가능하다. `int weq(10);`으로 초기화할 수 있다.
어떤 type이든 초기화에 같은 문법이 적용되도록 확장한 것이다.

마지막 new를 이용한 부분은 Ch9에서 built-in type이 저렇게 초기화 될 수 있음을 배웠다.
class에 대해서도 저런 문법이 가능하네. 저렇게 작성하면, constructor 이용해서 초기화 됨.
(p.669)`new`에 class 쓰면 전부 적절한 constructor가 알아서 호출돼 초기화한다.
그래서 사실은 위의 default constructor 사용 예시에서도 constructor가 사용된다.
(new를 사용하는 것이 초기화맥락이니 그렇게 사용되도록 적용된거지싶다)

>추가내용
argument 없이 만들면 default constructor가 호출되고,
argument 있이 만들면 일반 constructor가 호출된다.
그리고 default constructor는 내가 아무 constructor도 만들지 않았을때 자동 생성된다.
따라서 parameterized constructor만 만들어놓고 Stock stock3; 식으로 object를 만들면 이는 error이다.
parameterized constructor를 만들었단거 자체가, 특정 값으로 initialize되길 원하는 것이기 때문에 초기화가 강제된다.
이걸 원하지 않는다면 추가로 default constructor도 정의해주면 된다.

>정리
사실상 우리가 constructor를 만들든 안만들든 자동으로 만들어서라도 이용한다. 즉 모든 class object는 constructor를 거쳐서 만들어진다.
만든다면 default랑 parameterize 둘 다 만들어도되고, 하나씩만 만들어서 써도 된다.
대신 default만 만들게되면 arugment가 있는 형태의 문은 작성하지 못하고(default argument 쓴게 아니라면/copy ctor은 예외),
반대로 parameterize만 만들게 되면 일반적인 object 생성문은 작성하지 못한다.
(예를들어 Stock abc; 이런거.. 무조건 argument 넘겨줘야됨.)

참고로 ctor가 object를 만드는 것은 아니다. 단순히 초기화하는 역할일 뿐이다.

Destructors

Destructors in C++ - GeeksforGeeks
constructor에 의해 만들어진 object(사실상 모든 class object를 말함)가 만료될때 constructor로 만든 것들을 clean up하기위해 사용된다.
ex. new로 새로운 공간을 할당받았다면 그걸 해제해줘야 한다.

pointer나 dynamic allocated memory가 없다면 굳이 만들지 않아도 된다.
어차피 new로 할당받은게 아니라면 따로 해제할 필요가 없어서 할 것도 없고, 따로 안만들면 destructor도 아무것도 안하는게 자동으로 생성돼서 이걸 사용한다.

아래는 직접 destructor 만들때 필요한 사항들

  1. return type 작성 X
  2. 함수 이름은 class 이름과 같다. 함수 이름 앞에 ~가 붙는다.
    ex) Stock::~Stock() { ~ }
  3. parameter도 작성 X
  4. 딱히 말은 없긴한데 당연히 얘도 public에 적어야겠지?(예시코드에도 그렇게 함)
  5. 하나만 정의될 수 있다.

언제 호출되나?
compiler가 다루기때문에, 보통은 우리가 호출하지 않는다.
object 수명따라서 만료될때 자동으로 destructor가 호출된다.
automatic이면 block나갈때 호출되고, static이면 프로그램 종료될때 호출되고, new로 object 할당받았으면 delete 나올때 호출되고,,,
(automatic storage object는 해당 block이 끝날때 삭제되는데, 생성의 역순으로 삭제된다.(p.637))

destructor는 말 그대로 object가 만료될때 우리가 손으로 만들었던걸 손으로 지우는 것이다.
만료될때 자동으로 호출돼서 그 기능을 보조하는 것이지,
우리가 중간에 호출될 곳이 아닌데 호출했다고해서 object가 제거되진 않는다.

예시

Header File

// stock10.h
#ifndef STOCK10_H_   //header guard
#define STOCK01_H_
#include <string>
class Stock {
private:
    std::string company;
    long shares;
    double share_val;
    double total_val;
    void set_tot() { total_val = shares * share_val; }
public:
    Stock(); // default constructor
    Stock(const std::string & co, long n = 0, double pr = 0.0);
    ~Stock(); // noisy destructor
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    void show();
};
#endi

Implementation File

// stock10.cpp
#include <iostream>
#include "stock10.h"

// class constructors
Stock::Stock() { // default constructor
    std::cout << "Default constructor called\n";
    company = "no name";
    shares = 0;
    share_val = 0.0;
    total_val = 0.0;
}
Stock::Stock(const std::string & co, long n, double pr) {
    std::cout << "Constructor using " << co << " called\n";
    company = co;
    if (n < 0) {
        std::cout << "Number of shares can’t be negative; "
        << company << " shares set to 0.\n";
        shares = 0;
	}
	else
        shares = n;
    share_val = pr;
    set_tot();
}

// class destructor
Stock::~Stock() { // verbose class destructor
	std::cout << "Bye, " << company << "!\n";
}

// other methods
void Stock::buy(long num, double price) {
	if (num < 0) {
	std::cout << "Number of shares purchased can’t be negative. "
	<< "Transaction is aborted.\n";
	}
	else {
        shares += num;
        share_val = price;
        set_tot();
	}
}
void Stock::sell(long num, double price) { ... }
void Stock::update(double price) { ... }
void Stock::show() { ... }
client file(여긴 안적음)에 보면 main 함수 내에서 class object 사용할때,
return 문 이전에 {}로 한번 더 묶었다.(p.533보면 있음)
windowing environment라면(ms window 말하는거 아님),
main()의 작업이 끝나고 destructor가 호출되면 위 출력문이 print되지 않을 수도 있다.
그걸 방지하기위해 block으로 한번 더 묶어서 main이 종료되기 전에 destructor가 호출되도록 하는 것
(destructor가 main 종료되고서라도 호출은 되지만 그냥 print된거 보려는 의도인듯)
  • Stock stock2 = Stock("Boffo Objects", 2, 2.0); : initialization
    이렇게 constructor 함수 호출을 직접적으로 이용하는 경우, Standard는 compiler가 두가지 방법으로 구현하도록 한다.(둘다 일단 constructor는 호출함)
    1. 일반적인 Stock stack2("Boffo Objects", 2, 2.0);처럼 바로 initialize되도록 하는 방법
    2. constructor를 호출해서 temporary object를 만든다. 그걸 이용해 stock2에 copy하고 temporary object는 버린다.(이때 destructor 호출)
    (temporary object 삭제하는 작업은 바로가 아니라 좀 늦게 진행될 수도 있다. == destructor도 좀 늦게 호출될 수도 있음.)

  • stock1 = Stock("Nifty Foods", 10, 50.0); : assignment
    이 경우 stock1은 이미 만들어져있는 상태이므로, 새로운 값으로 assign만 되는 것이다.
    위와 마찬가지로 constructor를 직접 호출하는데, 얘는 initialize하는게 아니라서 2번 방법밖에 안된다.
    즉, 무조건 temporary object가 만들어지고, copy하고, 버린다.(이때도 당연히 destructor 함께 호출)
    (assingment보단 initialization이 보통은 더 효율적이다.)

temporary란 용어가 두번나오네,, reference variable에서 한번, constructor에서 한번..

List Initialization (C++11)

class object만들때도 list-initialization이 가능하다.
constructor의 argument list와 매칭되게 작성해야 한다.

1) Stock hot = {"Derivati", 100, 4.2};
2) Stock jack {"Sport Age"};
3) Stock temp {};

1과 2는 Stock::Stock(const std::string * co, long n = 0, double pr = 0.0};을 사용한다.
3은 default constructor를 사용한다.

const Member Functions

const Stock land = Stock("Klug"); 이렇게 object를 const로 선언한 경우
land.show(); 같은 멤버 함수를 사용할 수 없다.
왜냐하면 저 함수가 해당 object의 내용을 변경하는지 알 수 없기 때문이다.(const이므로 변경되면 안됨)

과거엔 argument를 const로 해서 해결했다는데,, 이게 문제인게 show같은 함수는 argument가 없음.
그래서 새로운 syntax가 필요해져서 const member function을 추가했다.
괄호 뒤에 const라고 적는다.
void show() const; : prototype과
void Stock::show() const { ~ } : definition에 둘 다 작성

변경하지 않는 변수는 const로 받듯이,
object를 변경하지 않는 함수라면 이렇게 const member function으로 해주는게 좋다.

즉, *this가 const로 되는 것이다.
그래서 그런가 직접해보니 friend function은 const로 선언이 안된다고하네..
friend는 그냥 접근 권한만 있을 뿐 *this를 인자로 받는게 아니니..

같은 이유로 const member 함수는 prototype과 definition에 `const`를 모두 기술한다.
하지만 virtual이나 default argument는 prototype에만 작성했었다.
(member initializer list는 거꾸로 definition에"만" 작성)
왜냐하면 prototype이 결국 compiler에 해당 함수 정보를 알려주는 것인데,
virtual이나 default argument 정보는 거기서 알면 충분했기때문이다.
하지만 const 멤버함수는 사실상 멤버함수 내의 인자인 *this를 const *this라고 하는 것이다.
그러므로 그걸 prototype에만 명시하게되면 함수 signature이 맞지않아 애초에 매칭이 안돼버린다.
그래서 virtual이나 default argument와는 달리 함수 definition에도 const를 기술한다.

정리

우리가 따로 정의하지 않아도 object가 생성되고 사라질때 constructor와 destructor는 자동으로 만들어서라도 호출된다.
constructor는 object가 생성될때 호출되고, destructor는 object가 사라질때 호출된다.
전자는 initialize 기능을 하고, 후자는 constructor에서 만든 new 같은걸 제거하기위해 사용된다.

constructor는 overloading해서 여러개 작성해도 괜찮다.
대신 default constructor와 destructor는 class당 하나씩만 존재할 수 있다.

constructor의 argument가 하나 뿐이라면 Stock tybby = 32; 식의 initialization도 가능(특별한 형태)
(근데 이 문법은 문제가 될 수 있어서 막는 방법 ch11에서 알려준다고 함.)


The this Pointer

예시를 하나 들어보자. 같은 class의 다른 object를 입력받아서 (특정 값을 기준으로) 크기를 비교하고, 더 큰 object를 반환하는 member 함수를 만든다고 해보자.
(효율을 위해 reference로 주고받자.)

//수정할 이유가 없어서 함수랑 arugment 모두 cosnt로,, return도 const로(s 반환해야되면 const여야됨)
const Stock & Stock::topval(const Stock & s) const {
	if (s.total_val > total_val)  //얘네 둘의 차이는 당연히 알테고
    	return s;
    else
    	return ???;  //return *this;

저기 ??? 자리에 이 member function을 호출한 object가 와야한다.
이걸 표현하는 방법이 this pointer이다.
this pointer는 해당 member function을 호출한 object를 가리킨다.
thismember function이 호출될때 hidden argument로 함수에 전달된다.

우리가 위에 if문에 적은 total_val사실은 this->total_val이다.
(structure에서의 관습을 class에도 그대로 가져왔다. 포인터로 멤버접근하려면 ->)
object 자체를 접근하려면 당연히 *this를 사용한다.

constructor와 destructor도 this pointer를 가진다.
함수에 const가 적용되면(const member function), this를 이용해서 object의 값을 바꿀 수 없다.

private member 함수도 this pointer를 가진다.
static member function만 this pointer가 없다.

An Array of Objects

Stock mystuff[5]; 일반 type의 배열처럼 만들 수 있다.
각 원소가 object이니, mystuff[0].show(); 식으로 사용 가능

initialization은 다음과 같이 한다.

Stock stocks[5] = {
		Stock("Nano", 12, 2.1),
        Stock(),
        Stock("Kip", 2, 5.5)
}

이런식으로 consturctor를 호출해서 사용한다.
(이렇게 constructor를 explicit하게 call하는 방식만 되나보다)
보면 알겠지만, 각 constructor는 default를 쓰든 뭘 쓰든 상관없다.
그리고 배열 뒷부분 비어있는 두 elements에는 default constructor가 호출된다.

> Member function과 C-style function
기존 UNIX가 사용한 cfront라는 것은 C++ program을 C program으로 변환한다. 어떻게 되는지 보자.

멤버 함수의 정의를 우리는

void Stock::show() const {
	.
    .
    .
}

이런 식으로 한다. 이게

void show(const Stock * this) {
	.
    .
    .
}

이렇게 바뀌는 것이다.
내부적으론 Stock::Stock의 포인터로 바껴서 parameter로 들어간다.(const도 있으면 같이)
이렇게 멤버 함수는 해당 class의 object를 전달받아서 거기에 접근할 수 있는 것이다.

하나 더 보자면,
top.show(); 같은 member 함수 호출은
show(&top); 이렇게 바뀐다.(this pointer에 object 전달됨)

보면 그냥 우리가 C에서 만들었던 그거네 전부다.
그냥 진짜 그 형식을 거의 가져와서 표면적으로도 이해하기 쉽게, 개념도 그렇고,, 그렇게 바꾼듯.

Class Scope

class내에 정의(선언)된 name들에 적용이 된다.
class 내의 name들은 안에서만 알려져있고, 밖에선 아니다.
그렇기때문에
1. 같은 class member "name"을 여러 곳에서 써도 괜찮고
2. class 내의 멤버를 밖에선 (public이더라도) 함부로 접근할 수 없다.
3. member function 정의할때 scope-resolution operator를 사용해야한다.

., ::, -> : 얘네는 문맥에 맞게 잘 사용하면 된다.

Why does C++ need the scope resolution operator?
: 답변 중 하나만 일단 여기 적어보자면, 다 .으로 해결하려니 object의 멤버인지 class의 멤버인지 모호한 부분이 있었다고 비야네 형님께서 직접 말씀하셨다고하네.. 즉, 기존 C에서 사용하던 의미인 특정 object의 멤버라는 의미와 class scope 같은 scope의 멤버(?)라는 명확히 의미를 구분하기 위해 따로 쓰는듯. ::은 말 그대로 scope resolution operator니까 class scope 같은 개념에 적용할 수 있는거지.

Class Scope Constants

class 내에서 symbolic constant를 만들려면? 배열을 예시로 보자.

class Bakery {
private:
	const int Months = 12;
    double costs[Months];
    . . .

이렇게 하면 안된다.
class는 틀을 만드는 것일뿐, object가 생성되기 전까진 아무 공간도 할당되지 않기때문이다.
(C++11에서 저렇게 in-class initialization을 허용하긴하지만, 그렇다고해도 위 array 선언문이 작동하진 않는다. 자세한건 ch12)

왜 안되는걸까?
정확히 배열이 어떻게 만들어지고, 언제 만들어지고 이런 정보를 잘은 모르지만,
class라는게 틀을 만드는 것이라 그런 것 같다.(책에서도 이렇게만 설명하는거보니 맞지싶네)
어떤 type으로 구성되는지 틀을 딱 짜놔야되는데,
저렇게 해버리면 object가 만들어질때까진 costs의 type을 정확히 알지 못한다.
(Months도 object가 만들어져야 할당이 되니까 결국은)
member함수 내에선 저렇게해도 잘 선언되는거보니(해봄) 위 이유가 맞는 것 같다. 틀의 문제.

애초에 왜 배열은 constant만 길이로 받는지 찾아보다가
https://flowerexcel.tistory.com/7
이런 글 읽어봤는데 내용 괜찮은듯,, 이런 탐구하는 자세(를 가지려고해도 일단은 뭘 많이 알아야되네)

그럼 어떻게?

1. enum을 이용한다. (enum은 뭐 object 상관없이 미리 값이 만들어지나?보네뭐)

class Bakery {
private:
	enum { Months = 12 };
    double costs[Months];
    . . .

이러면 enum은 class scope를 가지므로 class내에서 그냥 쓰일 수 있다.
이렇게 아무 tag 없이 만들면 class data member를 생성하지 않고, 그냥 macro처럼 내부 symbol이 대체된다.

2. static const를 이용한다.

class Bakery {
private:
	static const Months = 12;
    double costs[Months];
    . . .

이렇게 선언하면, object내 저장되는게 아니라, 다른 static 변수들과 같은 공간에 해당 value가 저장된다.
그래서 해당 변수를 (함수내에 static으로 선언된듯이) 모든 object가 이 변수를 공유한다.

링크보면 알겠지만 static const만 허용하고 있다.
(왜 static const만? : 링크)

Scoped Enumeratins (C++11)

enum egg {Small, Jumbo};
enum t_shirt {Small, Jumbo};

이렇게하면 두 enum의 enumerators가 충돌하기 때문에 C++11에선 enumerators가 class scope를 가지게해서 해결한다.

enum class egg {Small, Jumbo};       //class대신 `struct` keyword 사용해도 OK
enum class t_shirt {Small, Jumbo};

이러면 이제 각 enumerator를 egg::Small;식으로 접근해야한다.
(class 대신 struct가 와도 된다.)

원래 enumerator는 integer로 implicit conversion이 되지만, scoped enumeration은 자동으로 형변환이 안된다.(주의)
원한다면 int(t_shirt::Small)처럼 explicit type casting은 가능하다.

underlying integer type
scoped enumeration : int가 기본
unscoped enumeration : implementation define이 기본
(그렇기때문에 후자의 경우 system마다 size가 다를 수 있다.)

둘 다 : short 같은걸 추가해서 해당 enumeration의 underlying type을 명시해줄 수 있다.
ex)
enum class pizza : short {Small, Jumbo}; : scoped enumeration
enum pizza : short {Small, Jumbo}; : unscoped enumeration
name : type이네.. 책은 다르게 돼있음. 오타인듯


Abstract Data Types

ADT란 말그대로 특정 언어나 implementation detail을 베재한 일반적인 data type 설명을 말한다.
class는 ADT를 구현해내기에 좋다. (애초에 ADT 접근방식과 비슷)
private section을 통해 자세한 구현 사항이나 데이터 표현법을 숨기고, 표면적으론 "stack에 넣는다" 같은 기능을 하도록 하는 것이다.


Summary

class를 이용해 데이터와 연산을 하나로 묶는다.
보통은 data member를 private에(data hiding), 함수를 public에 배치
(private이 implementation이고, public이 interface에 대응된다.)

mr_object.try_me()를 OOP 용어로 "sending a try_me message to the mr_object object".라고 한다.


Chapter Review

class란?
: user-defined type이다. data가 어떻게 저장되는지, 그 data에 어떤 method가 가능한지를 기술한다.

abstraction : public에 가능한 data에 가능한 method를 나열함으로써 구현
data hiding : private에 data를 놔둬서 멤버함수를 통해서만 접근할 수 있도록 해서 구현
encapsulation : 구현 구체사항인 data representation이라던가 method code 등을 숨겨서(보통 다른 파일에 두지) 구현

멤버 함수와 멤버 변수 차이?
: 변수는 object마다 만들어지지만, 함수는 하나를 모든 object가 공유한다.
(또 뭐 보통 변수는 private에 놓고 함수는 public에 둔다.. 이정도.. 변수도 물론 public에 있으면 . operator로 접근 가능)

이렇게 private member의 alias를 만들어 주는 것도 가능하다.
즉 당연히 public 함수 통하면 private에도 접근할 수 있다.. 수정도 되고. public이 통하는 길이니 잘 관리하면 됨.
(p.1350 9번)


Programming exercises

string class는 std namespace에 속한다.. 기본적인거 주의하자. 그냥 string만 띡 써두고 왜 안되지 고민했네 ㅋㅋ

const나 parameter 관리 잘하기.. const parameter에 non const를 넘기는건 상관없지만 되도록 맞춰주면 좋고, parameter 받을때도 string object를 받는 곳에 char array를 넘겨주는건 상관 없지만 역은 당연히 안된다.

interface는 구현 세부사항과 무관해야한다. 내부 표현법 등을 다른 곳에 영향을 안미치고 바꿔야됨.
추가면 추가, 삭제면 삭제. 딱 이렇게 먼저 명시해두고, 세부사항 만드는게 나을지도..


영단어

analogy 비유, 유사점, 유추
share (주식에서) 주 (ex. 100share : 100주)
wrinkle 주름
insulation 절연 처리
absolve 무죄임을 선언하다, 용서하다
acquisition 습득
confine 국한시키다
debris 잔해
ought 해야한다, 의무, 책임
verbose 장황한

0개의 댓글