배움 저장소
[홍정모의 따라하며 배우는 C++] 8. 객체지향의 기초 본문
8.1 객체지향 프로그래밍과 클래스
C++에서 Struct(구조체) 내부에 멤버함수를 추가할 수 있다. 하지만 Struct에는 멤버 변수만 넣음이 일반적이다
멤버함수를 추가한다면 class를 사용해주자. C는 Struct에 멤버함수 추가를 금지한다
struct Friend
{
string name;
string address;
int age;
double height;
double weight;
void print()
{ cout << name << " " << address << " " << height << " " << weight << endl; }
};
Class 만들기
클래스를 구현하기위하여 class keyword를 사용하자
Access Specifier 접근지정자
- 접근 지정자는 세가지가 있다. public, private, protected
- 구조체에는 접근지정자가 없다.
Instanciation
- 클래스의 Initialization / declaration은 특별히 Instanciation이라 부른다.
- 이때 Object의 이름 / identifier를 Instace라 부른다.
멤버변수명 이름짓기
- 멤버변수명은 underscore( _ )를 활용하여 _name , name_ , m_name처럼 짓는다. 회사의 코딩스타일을 따르자.
멤버 함수가 클래스 내부에서 구현되면 inline으로 간주한다
class Friend
{
public: // access specifier
string _name;
string address_;
int m_age;
double height;
double weight;
void print()
{ cout << _name << " " << address_ << " " << m_age << " " << height << " " << weight << endl; }
};
int main()
{
Friend f{ "John", "DownTown", 15, 180.3, 90 };
f.print();
}
8.2 캡슐화, 접근 지정자, 접근 함수
Encapsulation, Access Specifiers, Access Functions
Encapsulation
- 여러 변수 그리고 이 변수와 상호작용할 함수를 함께 묶어 하나의 자료형으로 만드는 일이다. Access Specifiers(접근지정자)를 활용하여 데이터를 숨길 수 있다.
Private Access Specifier(접근지정자)를 유용하게 사용하기
- 멤버 변수를 public으로 지정하면 class 외부에서 해당 변수명에 접근하여 값을 바꿀 수 있다. 멤버 변수를 변경하는 코드가 많은 상황을 생각해보자. 다른 class에도 동일한 변수명을 가지고 있다면 여러 파일을 뒤져가며 해당 변수명의 이름을 바꾸어주어야 한다. private로 지정하였다면 class 내부에서만 변수명을 변경해주면 된다.
멤버함수 getDay( )의 정의를 보자: 참조된 값을 반환하면 멤버변수를 임의로 변경할 수 있다. 상수로 지정해주자.
멤버함수 copyform( )의 정의를 보자: 다른 instance의 private 멤버변수에 접근하였다. 같은 Type이면 이를 허용한다
class Date
{
private:
int _day;
int _month;
int _year;
public:
// Access function
void setDate(const int& d, const int& m, const int & y)
{
_day = d;
_month = m;
_year = y;
}
const int& getDay() // block to modifing member variable in outside
{ return _day; }
void printDate()
{ cout << _day << " " << _month << " " << _year << endl; }
void copyFrom(const Date& date) // copy assignmnet
{
_day = date._day; // this is so strange because this variable
_month = date._month; // is private. so you can't access it outside of class
_year = date._year; // let notice this out, Same Class type allow to access it
}
};
int main()
{
Date today;
today.setDate(24, 12, 2021);
today.printDate();
Date copied;
copied.copyFrom(today);
copied.printDate();
}
8.3 생성자 Constructors
Constructors(생성자): 클래스가 instanciation 될 때 호출되는 함수이다
생성자가 필요한 이유 - class(객체) 내부 변수를 Private로 지정해보자
- access specifier(접근지정자)가 private로 설정되면 초기화 때 값을 지정할 수 없다. 다음 코드는 에러를 발생시킨다
class Fraction
{
private:
int m_numerator;
int m_denominator;
};
int main()
{
Fraction frac{1,4}; // Error! too many initializer values
}
public으로 지정하면 에러가 사라진다
- 생성자가 없을 때 기본 생성자와 uniform initialization으로 public 멤버변수를 초기화할 수 있다.
매개변수가 맞는 생성자를 구현하면 멤버 변수에 직접 값을 할당해줄 수 있다
- 위와 같이 클래스를 에러없이 선언/instancing 할 수 있다.
- Uniform initalization은 타입변환을 해주지 않음을 기억하자.
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction(const int& numer=1, const int& domi=1)
{
m_numerator = numer;
m_denominator = domi;
}
};
int main()
{
Fraction frac{1,2};
}
클래스는 constructor(생성자)를 가질 수 있다. 생성자는 해당 클래스가 선언될 때 실행된다.
- 다음 예제를 debugging하면 Fraction frac이 선언될 때 Fraction( ) 생성자 함수가 실행됨을 확인할 수 있다.
- 생성자를 구현하지 않으면 기본값으로 비어있는 생성자 Fraction( ){ }를 구현해놓는다.
생성자를 하나라도 구현하면 기본생성자는 구현되지 않는다.
- 다음을 주의하자. 매개변수가 없는 생성자를 호출할 때는 반드시 클래스 식별자 뒤에 parentheses ( )를 빼야 한다.
기본값을 정해두어서 매개변수를 입력하지 않는 생성자를 호출할 때도 위를 따라야 한다.
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction()
{
m_numerator = 0;
m_denominator = 1;
}
};
int main()
{
Fraction frac;
}
매개변수가 없는 생성자와, 기본값이 모두 정해져있는 생성자가 구현되어있다면 생성자 호출시 에러가 발생한다
class Fraction
{
private:
int m_numerator;
int m_denominator;
public:
Fraction()
{
m_numerator = 0;
m_denominator = 1;
}
Fraction(const int& numer=1, const int& domi=1)
{
m_numerator = numer;
m_denominator = domi;
}
};
int main()
{
Fraction frac;
}
class(클래스) 내부에서 생성되는 class
class Second
{
public:
Second() { cout << " class Second Constructor " << endl; }
};
class First
{
Second Sec;
public:
First() { cout << " class First Constructor " << endl; }
};
int main()
{
First fir;
}
위 코드의 출력결과이다. class First 내부에 있는 멤버변수 class Second가 먼저 생성된다
- class First 내부에서 Second 멤버변수를 사용하려면 instance로 만들어져있어야 한다.
class Second Constructor
class First Constructor
8.4 생성자 멤버 초기화 목록
Member Initializer List
Member Initializer List를 사용해보자. 멤버 변수를 생성자 Body 직전에 초기화 해줄 수 있다
- 클래스 내부에서 멤버변수를 초기화하고 생성자에서도 Member Initializer List를 만들 수 있다. 이 때 Member Initializer list에 멤버 변수의 값이 지정되어 있다면 맴버 변수가 선언될 때 멤버 변수에 할당된 값을 사용하지 않고 Member Initializer List에서 값을 할당받는다.
- <initializer_list>라이브러리는 9.12에서 다룬다.
class inner
{
private:
int _i;
public:
inner(const int& i): _i(i){}
};
class Initializer_List
{
private:
int _i = -1;
double _d = -1.0;
char _c = '-';
int _arr[5] = {-1,};
inner _inn {-1};
public:
//Initializer_List() : _i(1), _d(3.14), _c('A')
Initializer_List() : _i{1}, _d{3.14}, _c{'A'}, _arr{0,1,2,3,4}, _inn{_i + 9}
{ // C++11 allow array's Initalizer list
cout << "Initialzer list is worked!" << endl;
cout << _i << " " << _d << " " << _c << " " << _arr << " " << &_inn << endl;
}
};
int main()
{
Initializer_List init_list;
}
8.5 위임 생성자
Delegating Constructors
위임 생성자는 다른 생성자를 호출하는 생성자이다
- 특정 기능을 구현한 코드가 여러군데 있으면 관리하기 어렵다. 위임생성자를 사용해주자
- C++ 11 이상 지원하는 기능이다
class Student
{
private:
int _id;
string _name;
public:
Student(const string& name) : Student(0, name) {} // Delegating Constructor
Student(const int& id, const string& name) : _id(id), _name(name) {}
};
Delegating Constructor를 사용하지 않으면 init 함수를 구현하여 사용할 수도 있다
class Student
{
private:
int _id;
string _name;
public:
Student(const string& name)
{ init(0, name); }
Student(const int& id, const string& name)
{ init(id, name); }
void init(const int& id, const string& name)
{
_id = id;
_name = name;
}
};
8.6 소멸자 destructor
Instance가 영역을 벗어나 메모리를 반환할 때 호출되는 멤버함수이다.
두 개의 instance를 생성하고 소멸 순서를 확인하자
class Simple
{
private:
int _id;
public:
Simple(const int& id): _id(id)
{ cout << "Constructor executed id=" << _id << endl; }
~Simple()
{ cout << "Destructor executed id=" << _id << endl; }
};
int main()
{
Simple s1(0);
Simple s2(1);
}
출력값은 아래와 같다. 먼저 생성된 instance가 나중에 삭제된다. Stack처럼 작동한다.
Constructor executed id=0
Constructor executed id=1
Destructor executed id=1
Destructor executed id=0
소멸자의 활용
- Class 내부에서 동적할당된 메모리를 해제해줄 수 있으므로 Memory Leak를 걱정해주지 않아도 된다. 동적할당 메모리를 관리해주지 않으려면 vector library를 사용하자. 메모리 해제 기능이 구현되어 있다.
class Arr
{
private:
int *_arr;
int _length;
public:
Arr(const int length) : _length(length), _arr(nullptr)
{
_arr = new int[_length];
}
~Arr()
{
if(_arr != nullptr) // delete nullptr makes error!
delete [] _arr;
}
};
8.7 this 포인터와 연쇄 호출
Chaining Member Functions
this 포인터는 instance가 사용하기 위해 할당받은 메모리 주소를 반환한다.
class ID{
private:
int _id;
public:
ID(int id){
setID(id);
cout << this << endl;
}
void setID(int id){ _id = id; }
int getID(){ return _id; }
};
int main(){
ID id_five(5);
ID id_ten(10);
cout << &id_five << endl;
cout << &id_ten << endl;
}
007BFD10
007BFD04
007BFD10
007BFD04
클래스 내부에 구현된 함수와 변수는 모두 this -> function( ), this -> varaible 형태로 호출된다
- 코드를 작성할 때 this를 생략할 수 있다. 아래 클래스 코드는 instance 저장공간을 어떻게 사용할 건지에 대한 설계도이다. 별도로 저장되어 있으며 instance 저장공간과 독립적이다. instanciating 때 아래 코드가 호출되면 this 포인터는 instance가 사용하기 위해 할당받은 메모리주소를 반환한다.
class ID{
private:
int _id;
public:
ID(int id){ this->setID(id); }
void setID(int id){ this->_id = id; }
int getID(){ return this->_id; }
};
Instance마다 각각의 멤버함수를 가지고 있을까? 아니다. 개념적으로 멤버함수는 아래 예제처럼 구현되어 있고 해당 객체를 인자로 받아 호출된다. 문법상으로 C++에서 사용할 순 없다. "8.11 정적 멤버 함수"에서 멤버함수에 접근하는 방법을 다룬다.
ID::setID(&id_five, id);
this 포인터를 활용하여 연쇄 호출 해보기
- 아래 코드와 같이 멤버변수를 호출하기 위해서 매 번 Instance의 식별자를 입력해야 한다.
class Calculator{
private:
int _value;
public:
Calculator(int value): _value(value) {}
void add(int value) { _value += value; }
void sub(int value) { _value -= value; }
void mul(int value) { _value *= value; }
void print(){ cout << _value << endl; }
};
int main(){
Calculator Calc(0);
Calc.add(5);
Calc.sub(2);
Calc.mul(2);
Calc.print();
}
Chaining Member Function
- this 포인터를 이용하여 자기 자신을 반환하면 연쇄호출이 가능하다
class Calculator{
private:
int _value;
public:
Calculator(int value): _value(value) {}
Calculator& add(int value) { _value += value; return *this; }
Calculator& sub(int value) { _value -= value; return *this; }
Calculator& mul(int value) { _value *= value; return *this; }
Calculator& print(){ cout << _value << endl; return *this; }
};
int main(){
Calculator Calc(0);
Calc.add(5).sub(2).mul(2).print();
// OR
Calculator(10).add(5).sub(2).mul(2).print();
}
8.8 클래스 코드와 헤더 파일
클래스는 헤더 파일과 코드파일을 따로 만들어 관리하자
Calculator.h
#pragma once
#include <iostream>
class Calculator {
private:
int _value;
public:
Calculator(int value) : _value(value) {}
Calculator& add(int value);
Calculator& sub(int value);
Calculator& mul(int value);
Calculator& print();
};
Calculator.cpp
#include "Calculator.h"
#pragma once
#include <iostream>
using namespace std; // doesn't impact any file
// if it place in the global scope of header
// impact every file that include that header
Calculator::Calculator(int value) : _value(value) {}
Calculator& Calculator::add(int value)
{
_value += value;
return *this;
}
Calculator& Calculator::sub(int value)
{
_value -= value;
return *this;
}
Calculator& Calculator::mul(int value)
{
_value *= value;
return *this;
}
Calculator& Calculator::print() {
cout << _value << endl;
return *this;
}
main.cpp
#include <iostream>
#include "Calculator.h"
int main(){
Calculator Calc(0);
Calc.add(5).sub(2).mul(2).print();
Calculator(10).add(5).sub(2).mul(2).print();
}
8.9 클래스와 const
아래와 같이 class를 정의하였다
class Value{
public:
int _value = 0;
void setValue(int value){ _value = value; }
int getValue()
{
return _value;
}
};
위 클래스의 상수 인스턴스를 만들고 멤버 변수를 바꾸어보자. 에러가 발생한다
const Value value;
value.setValue(5); // Error
아래의 코드는 에러를 발생시킨다. 값을 바꾸지 않았음에도 에러가 발생한다
cout << value.getValue() << endl; // Error??
상수로 지정된 인스턴스는 상수로 정의된 함수만 호출할 수 있다.
- 함수명 뒤에 const keyword를 추가해주면 상수 인스턴스가 해당 함수를 호출할 수 있다.
- 함수명 뒤에 const keyword를 추가하면 함수 내부에서 멤버변수를 변경할 수 없다.
class Value{
public:
int _value = 0;
void setValue(int value){ _value = value; }
int getValue() const
{
return _value;
}
};
int main(){
const Value value;
cout << value.getValue() << endl;
Copy Constructor (복사 생성자)
- printValue 함수에서 매개변수는 값을 복사하여 사용하고 있다. 그렇다면 생성자가 2번 호출되어야 한다. 출력값을 보면 "Constructor!"가 한 번만 출력되는 걸 확인할 수 있다. 분명 인수의 주소값과 함수 외부 객체의 주소 값은 다르다. 어떻게 된 걸까?
class Value{
public:
int _value = 0;
Value(){ cout << "Constructor!" << endl; }
void setValue(int value){ _value = value; }
int getValue() const{ return _value; }
};
void printValue(Value v)
{
cout << &v << endl;
}
int main(){
Value value;
cout << &value << endl;
printValue(value);
}
Constructor!
00FCFB44
00FCFA6C
Implicit Default Copy Constructor
- Copy Constructor는 기본값으로 생성된다. 위 예제에서 Copy Contructor를 구현하지 않았기 때문에 기본값이 작동하였다. 아래와 같이 직접 복사 생성자를 구현해보자
class Value{
public:
Value(){ cout << "Constructor!" << endl; }
Value(const Value& v)
{
*this = v;
cout << "Copied Constructor on " <<this << endl;
}
int _value = 0;
void setValue(int value){ _value = value; }
int getValue() const
{ return _value; }
};
void printValue(Value v)
{
cout << &v << endl;
}
int main(){
Value value;
cout << &value << endl;
printValue(value);
}
Constructor!
008FFDA8
Copied Constructor on 008FFCC4
008FFCC4
함수의 오버로딩을 활용하여 class의 상수 여부 관계없이 멤버함수가 동작하게 구현하기
- 매개변수가 다를 때 함수의 오버로딩이 가능했다. 클래스 내부에서 상수 함수와 그냥 함수를 오버로딩을 할 수 있다.
- 아래 예제를 보자. 일반 변수 str은 첫 번째 멤버함수를 호출한다. 상수 변수 const_str은 두 번째 상수 멤버함수를 호출한다.
class STR{
public:
string _str = "default";
const string& getValue() const { return _str; }
string& getValue() { return _str; }
};
int main(){
STR str;
str.getValue() = "is Changed";
cout << str._str << endl; // >> is Changed
const STR const_str;
const_str.getValue(); // Const variable!
}
str 변수는 값을 변경할 수 있지만, const_str 변수는 값을 변경할 수 없다.
8.10 정적 멤버 변수
다음은 정적 변수를 함수 내부에 구현한 예제이다
- 정적변수를 초기화하는 코드는 한 번만 실행됨을 확인할 수 있다.
int generateID(){
static int id = 0;
return ++id;
}
int main(){
cout << generateID() << endl; // >> 1
cout << generateID() << endl; // >> 2
cout << generateID() << endl; // >> 3
cout << generateID() << endl; // >> 4
}
class 내부에서 static variable(정적 변수)를 사용해보자
- 코드 파일에 class body가 구현되었다면 정적 변수는 클래스 외부에서 초기화되어야 한다.
- header 파일에 class body가 구현되었다면 정적 변수는 코드 파일에서 초기화되어야 한다.(redefinition 에러 방지)
- header 파일에 정적 변수는 상수로만 초기화될 수 있다.
class test{
public: // initializing static variable
static int _value; // inside of class would makes Error
};
// initialize this value out side of the class
int test::_value = 5;
int main(){
// Static variable exist already
cout << &test::_value << " " << test::_value << endl;
test t1;
test t2;
t2._value = 5;
cout << &t1._value << " " << t1._value << endl;
cout << &t2._value << " " << t2._value << endl;
cout << &test::_value << " " << test::_value << endl;
}
이 때 정적멤버변수는 Instance를 통해 접근하는 것 보다, Class를 통해 접근함이 더 바람직하다.
test t1;
t1._value; // Bad
test::_value; // Good
One Definition Rule
- C++에서 모든 변수는 단 한 번만 정의할 수 있다. 이를 One Definition Rule이라 한다. 만약 객체 내부에서 static varaible(정적변수)를 초기화하게 되면 ODR을 위반한다. 객체 외부에 static variable을 초기화하여 정적 변수의 주소값을 외부에 남기고 참조 하는 형식이 된다.
https://www.stroustrup.com/bs_faq2.html#in-class
Stroustrup: C++ Style and Technique FAQ
Morgan Stanley | Columbia University | Churchill College, Cambridge home | C++ | FAQ | technical FAQ | publications | WG21 papers | TC++PL | Tour++ | Programming | D&E | bio | interviews | videos | quotes | applications | guidelines | compilers Modified No
www.stroustrup.com
static const 멤버변수일 경우 객체 내부에서 정의해야 한다
class test{
public:
static const int _value = 1;
};
컴파일 타임에 결정되어야 하는 constexpr도 마찬가지이다
class test{
public:
static constexpr int _value = 1;
};
8.11 정적 멤버 함수
정적 멤버 변수가 public일 때는 {class name} :: {static variable name} 형태로 접근할 수 있다. private에선 불가능하다. private로 지정되었을 때 정적 멤버 변수 값을 가져올 수 있는 방법이 있다. 정적 멤버 함수를 구현하자
class test{
// public: //OK
private: // casuing Error!
static int s_value;
public:
int getValue() { return s_value; }
};
int test::s_value = 1;
int main(){
cout << test::s_value << endl; // Error in here
}
정적 멤버 함수를 구현하면 Instance 없이도 private로 설정된 정적 멤버 변수를 가져올 수 있다
class test{
private:
static int s_value;
public:
static int getValue() { return s_value; }
};
int test::s_value = 1;
int main(){
cout << test::getValue() << endl;
}
이 때 정적 멤버 함수 내부에서 this 포인터를 사용할 수 없다. 따라서 non-static 멤버 변수에 접근할 수 없다.
class Test{
private:
static int s_value;
int m_value;
public:
static int getValue() {
return s_value + m_value; // Error!
return s_value + this->m_value; // Same as above
}
int temp(){
cout << this->s_value << endl;
return this->s_value;
}
};
int Test::s_value = 1;
this 포인터를 사용할 수 없는 이유는 무엇일까? 우선 멤버함수가 어떻게 구현되었는지를 알아보자.
Instance와 분리된 멤버함수 사용해보기
- 아래 예제에서 Test의 멤버함수 temp의 주소를 함수 포인터에 저장하였다. 이후 각 Instance에서 해당 함수를 호출하여 사용하였다. 하나의 함수 주소이지만 서로 다른 Instance에서 동작함을 알 수 있다.
- 내부적으로 indirect 된 fptr을 실행하면 각 Instance의 주소를 인자로 넘겨받아 해당 함수를 실행시킨다. 함수 내부에서 넘겨받은 Instance의 주소는 "this 포인터" 형태로 사용한다.
class Test{
private:
static int s_value;
public:
static int getValue() { return s_value; }
int temp(){
cout << this->s_value << endl;
return this->s_value;
}
};
int Test::s_value = 1;
int main(){
Test t1;
Test t2;
int (Test::*fptr)() = &Test::temp;
(t1.*fptr)();
(t2.*fptr)();
}
이때 *fptr은 반드시 Instance와 함께 실행시켜주어야 한다. 함수명이 Test::*fptr로 작성된건 그 때문이다.
참고) C++에서 클래스 멤버 함수의 포인터는 주소값으로 형변환되지 않는다. 함수 포인터는 형변환을 해주지만 멤버 함수의 포인터는 안 해주기에 꼭 &를 붙여야 한다.
정적 멤버 함수는 Instance 없이도 사용가능하다
- 첫 번째 코드를 보자. 특정 instance 없이 정적멤버함수를 실행할 수 있다.
- 두 번째 코드를 보자. 함수명을 보면 특정 instance가 필요하지 않음을 확인할 수 있다.
- 세 번째 코드를 보자. 첫 번째 코드와 동일하다.
Test::getValue();
static int(*s_fptr)() = &Test::getValue;
s_fptr();
활용 예제
class IDGenerator
{
private:
static int s_nextID; // declaration for a static member
public:
static int getNextID(); // declaration for a static function
};
// definition of the static member outside the class. don't use the static keyword here.
int IDGenerator::s_nextID{ 1 };
// definition of the static function outside of the class. don't use the static keyword here.
int IDGenerator::getNextID() { return s_nextID++; }
int main()
{
for (int count{ 0 }; count < 5; ++count)
std::cout << "The next ID is: " << IDGenerator::getNextID() << '\n';
return 0;
}
정적 멤버 함수와 정적 멤버 변수는 모두 Instance가 아니라 Class에 속해있다
Class 내부에서 정적멤버변수를 초기화해보자
- Class 내부에서 임시 Class를 만들어 정적 멤버 변수를 초기화하는 Trick
(1) 임시 Class를 정의한다. 이 때 임시 Class 내부에서 정적 멤버 변수를 초기화 하자.
(2) 임시 class의 Instance를 멤버변수로 선언하되 static으로 만들자.
(3) Class 외부에서 해당 정적 Instance의 정의가 있어야 한다. 그래야 class 외부 주소값을 가진다.
(4) 이 때 Class 내부에서 초기화 하려는 정적 멤버 변수도 class 외부 주소값을 가져야한다.
class Test{
public:
class _inner{
public:
_inner(){ s_value = -1; }
};
private:
static int s_value;
static _inner s_iner;
public:
static int getValue() {
return s_value;
}
};
int Test::s_value; // Declaration outside of the class
Test::_inner Test::s_iner; // Declaration outside of the class
// Call Constructor
int main(){
static int(*s_fptr)() = &Test::getValue;
s_fptr();
}
8.12 친구 함수와 클래스 friend
friend keyword는 다른 클래스 혹은 다른 함수가 해당 객체의 private 멤버변수에 접근을 허락한다
- class 외부에서 구현된 함수 혹은 다른 클래스가 private 멤버변수에 접근해야 한다면 어떻게 해야 할까? freind keyword를 사용함이 유용하다.
- 연산자 오버로딩을 구현할 때 이 방법을 자주 사용한다
friend keyword를 사용하여 객체 내부에서 함수를 정의할 수 있다. 이 때 객체 내부에 정의하였어도 해당 객체의 멤버함수가 아님을 유의하자. friend keyword가 붙은 함수는 전역함수처럼 취급된다
friend keyword 활용해보자.
- 아래 keyword가 붙은 함수는 class A와 class B의 private 멤버변수에 접근 가능하다
- 이때 class A가 class B를 찾을 수 있도록 전방선언 해주자
class B; // forward declaration
// Bad for debugging
class A{
private:
int m_value = 1;
friend void function(A& a, B& b);
};
class B {
private:
int m_value = 2;
friend void function(A& a, B& b);
};
void function(A& a, B& b)
{
cout << a.m_value << endl;
}
int main(){
A a;
B b;
function(a, b);
}
class A에서 friend keyword로 class B를 등록하였다. class B는 A의 private 멤버변수에 접근할 수 있다
class B;
class A{
private:
int m_value = 1;
friend class B;
};
class B {
private:
int m_value = 2;
public:
void access_A_Value(A& a)
{
cout << a.m_value << endl;
}
};
int main(){
A a;
B b;
b.access_A_Value(a);
}
class A의 private 멤버변수를 class B가 아닌 class B의 특정 함수에게만 공개할 수 있다
class A;
class B {
private:
int m_value = 2;
public:
void access_A_Value(A& a);
};
class A {
private:
int m_value = 1;
friend void B::access_A_Value(A& a);
};
void B::access_A_Value(A& a)
{
cout << a.m_value << endl;
}
int main(){
A a;
B b;
b.access_A_Value(a);
}
이 때 class A와 class B의 정의 순서가 바뀌면 에러가 발생한다
- class A가 정의되는 시점에 class B에 멤버가 정의되었음을 확인할 수 없다.
- B::access_A_Value( ) 함수가 A 내부에서 정의되면 에러가 발생한다.
class B;
class A {
private:
int m_value = 1;
friend void B::access_A_Value(A& a);
};
class B {
private:
int m_value = 2;
public:
void access_A_Value(A& a);
};
void B::access_A_Value(A& a)
{
cout << a.m_value << endl; // Error!
}
정리하자면 다음과 같은 순서를 따라야 한다
- class A와 class B의 순서가 다르면 에러가 발생한다
class A; 전방선언
class B {
B의 멤버함수
};
class A {
friend + (B의 멤버함수)
};
B::B의 멤버함수
{
A의 Private 멤버 변수에 접근
}
쉽게 해결하는 방법
- header 파일에 class를 선언하고 코드 파일에서 class 정의를 구현하자. class를 각 파일별로 구분하여 관리하면 된다.
그러면 class 순서 때문에 위와 같이 순서를 조절할 필요가 없다.
class A가 class B를 friend로 지정하고 class B가 class C를 지정한 상황일 때 class A의 friend는 class C일까? 아니다
8.13 익명 객체
한 번만 사용할 객체는 익명으로 사용할 수 있다
class A{
public:
void print()
{
cout << "Hello" << endl;
}
};
int main(){
A().print(); // A() is R-value, Temporary things
}
익명객체를 활용한 예제이다
class Number{
private:
int m_number;
public:
Number(int number) { m_number = number; }
int getNum() const { return m_number; }
};
Number add(const Number &num1, const Number &num2)
{
return Number( num1.getNum() + num2.getNum() );
}
int main(){
cout << add( Number(5), Number(6) ).getNum() << endl;
}
8.14 클래스 안에 포함된 자료형 nested types
클래스 외부에서 사용할 일이 없는 사용자 정의 자료형은 클래스 내부에 정의하자
class Fruit
{
public:
enum class FruitType
{
APPLE, BANANA, CHERRY,
};
class InnerClass{};
struct InnerStruct{};
private:
FruitType m_fruit;
public:
Fruit(FruitType fruit) : m_fruit(fruit){}
FruitType getType(){ return m_fruit; }
};
int main()
{
Fruit fruit(Fruit::FruitType::APPLE);
}
8.15 실행 시간 측정하기
이 때 Debug 모드보다 Release 모드가 훨신 빠르다. 실행 시간은 주로 Release 모드를 기준으로 측정한다.
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono> // the god of time
using namespace std;
class Timer{
using clock_t = std::chrono::high_resolution_clock;
using second_t = std::chrono::duration<double, std::ratio<1>>;
std::chrono::time_point<clock_t> start_time = clock_t::now(); // capture current time for start
public:
void elapse(){
std::chrono::time_point<clock_t> end_time = clock_t:: now(); // capture current time for end
cout << std::chrono::duration_cast<second_t>(end_time - start_time).count() << endl;
// calculate seconds
}
};
int main(){
random_device rnd_device; // class for random number
mt19937 mersenne_engine{ rnd_device() }; // random number generator
vector<int> vec(20);
for(unsigned int i=0; i<vec.size(); ++i){ vec[i] = i; }
std::shuffle(begin(vec), end(vec), mersenne_engine);
for(auto& i:vec){ cout << i << " "; }
cout << endl;
Timer timer; // start for elapsing time
std::sort(begin(vec), end(vec));
timer.elapse(); // end to elapseing time
for (auto& i : vec) { cout << i << " "; }
cout << endl;
}
'Programming Language > C++' 카테고리의 다른 글
[홍정모의 따라하며 배우는 C++] 10.객체들 사이의 관계에 대해 (0) | 2021.12.26 |
---|---|
[홍정모의 따라하며 배우는 C++] 9.연산자 오버로딩 (0) | 2021.12.25 |
[홍정모의 따라하며 배우는 C++] 7. 함수 (0) | 2021.12.23 |
[홍정모의 따라하며 배우는 C++] 6. 행렬, 문자열, 포인터, 참조 (0) | 2021.12.19 |
[홍정모의 따라하며 배우는 C++] 5.흐름제어 (0) | 2021.12.17 |