OOP Intro
- Abstraction : class의 모델링 과정, 필요한 것만 보여주고 세부적인 정보는 몰라도 가능하게 하는 것
- Encalsulation : 데이터 숨기기
- Inheritance : 상속
- Polymorphism : 같아 보이지만 다른 동작을 함
- Function overloading -> static
- Function overriding -> dynamic
Object Alignment
- object가 메모리에 allocation 되는 동작 코드
- 각 멤버변수들은 자신의 바이트 값의 배수에서 시작해야 함
- 해당 오브젝트의 메모리 크기는 가장 큰 멤버 변수의 배수로 끝남
- false sharing
Cache line이라는 하드웨어적 구조로 데이터를 64 바이트로 잘라 다른 코어로 들어감
Cat의 경우 64 바이트로 잘랐을때 중간의 값을 잘라버리게 되는 문제가 발생
#include<iostream>
#include<array>
class Cat
{
public:
void speak();
private:
//<case1>
//double d8;
//int i4a;
//int i4b;
//<case2>
int i4a; //0~4 int, 4~8 padding
double d8; //8~16 double
int i4b;//16~20 int, 20~24 padding
};
int main()
{
Cat stackCat;
//std::cout << sizeof(stackCat) << std::endl; //case1 : 16 byte
std::cout << sizeof(stackCat) << std::endl; //case2 : 24 byte ->by memory padding
//<advanced case>
std::array<Cat, 100> cats; //false sharing 발생
//false sharing 해결 코드 예시
/*class alignas(32) Cat
{
};*/
return 0;
}
Static Members
- Static in Class
- Static member function :
오브젝트를 가리키는 this 정보가 없다. 오브젝트와 연관이 되어있지 않다.
-> 멤버 변수, 함수에 접근 불가능
-> 오브젝트 만들지 않아도 사용 가능
- Static member variable :
-> 프로그램이 실행되기 전에 초기화 필요
-> 아무곳에서나 접근이 가능해지므로, 코드 안정성을 위해서 static variable을 사용하는 function 안에서 선언하는 방식으로 안정성을 높일 수 있다. (해당 static variable이 function안에서만 사용될 때)
- Static variable in a function :
-> 코드의 안정성을 위해서 사용
class Cat
{
public:
void speak()
{
static int safeCount = 0; //Static variable in a function
count++;
safeCount++;
std::cout << "meow" << " count : " << count<<" , safe : "<<safeCount << std::endl;
}
static void staticSpeak() //Static member function
{
std::cout << "CAT" << std::endl;
//std::cout << mAge << std::endl; 멤버 변수에 접근 x
//speak(); 멤버 함수 접근 x
}
static int count; //Static member variable
private:
int mAge;
};
int Cat::count = 0; //static member variable 초기화
int main()
{
//Static Member Function 예시 코드
//Cat kitty;
//kitty.speak();
//kitty.staticSpeak();//오브젝트 만들어서 사용 가능
//Cat::staticSpeak(); //오브젝트 만들지 않아도 사용 가능
//---------------------------------------------------
//Static Member Variable 예시 코드
Cat kitty;
Cat nabi;
kitty.speak(); //meow 1
nabi.speak(); //meow 2
Cat::count = 0; //아무대서나 접근 가능
//Cat::safeCount = 0; 아무대서나 접근 불가능! by "static member variable in function"
return 0;
}
Member Init List
- 예시 코드
- 대입 연산을 이용해서 초기화를 하는 경우, 경우에 따라 필요없는 과정이 생긴다.
-> 아래 코드에서 Zoo 생성자 예시(임시 객체 생성 -> mkitty에 복사 -> 삭제)
-> member initialize를 사용하면 더욱 효율적으로 초기화 할 수 있다.
class Cat
{
public:
/*Cat()
{
mAge = 1; //대입
}*/
Cat() //member initialize
:mAge(1)
{}
/*Cat(int age)
{
mAge = age; //대입
}*/
Cat(int age) //member initialize
:mAge(age)
{}
private:
int mAge;
};
class Zoo
{
public:
//Zoo(int kittyage)
//{
// mkitty = Cat(kittyage); //임시 객체 생성 후 mkitty에 복사 후 삭제
//}
Zoo(int kittyage)
:mkitty(Cat(kittyage)) //member initialize -> 임시 객체 생성 x
{
}
private:
Cat mkitty;
};
Copy/Move Constructor & Assignment
- raw 포인터를 사용하지 않는 class를 만들 때 자동으로 만들어지는 것
1) constructor
2) destructor
3) copy/move constructor
4) copy/move assignment
-> raw 포인터가 멤버 변수로 있다면 상황에 맞게 위의 메소드들을 구현해야 한다.
- default 키워드 :
-> 자동으로 만들어지는 default constructor를 default로 사용
-> 새롭게 만든 constructor가 파라메터가 있는 경우, 파라메터 없는 생성자를 call하는 방법
- object 생성시에는 { }이용해서 생성하는 방식이 좋다.
-> '=' 을 이용하는 경우 copy constructor인지 assignment인지 헷갈리기 때문에
- 예시 코드
#include<iostream>
#include<string>
class Cat
{
public:
Cat() = default; //default constructor
Cat(std::string name, int age) //constructor
:mName{ std::move(name) }
, mAge{ age }
{
std::cout << mName << " constructor" << std::endl;
}
~Cat() noexcept//destructor
{
std::cout << mName << " destructor" << std::endl;
}
Cat(const Cat& ref) //copy constructor
:mName(ref.mName)
,mAge(ref.mAge)
{
std::cout << mName << " copy constructor" << std::endl;
}
Cat(Cat&& other) noexcept //move constructor
:mName{ std::move(other.mName) }
, mAge{ other.mAge }
{
std::cout << mName << " move constructor" << std::endl;
}
Cat& operator=(const Cat& other) //copy assignment
{
mName = other.mName;
mAge = other.mAge;
std::cout << mName << " copy assignment" << std::endl;
return *this;
}
Cat& operator=(Cat&& other) noexcept //move assignment
{
if (&other == this) //self assignment 방지
return *this;
mName = std::move(other.mName);
mAge = other.mAge;
std::cout << mName << " move assignment" << std::endl;
return *this;
}
void print()
{
std::cout << mName << " " << mAge << std::endl;
}
private:
std::string mName;
int mAge;
};
int main()
{
//copy, move constructor 예시 코드
Cat kitty{ "kitty", 1 };
Cat nabi; //default constructor로 문제 해결
Cat kitty2{ kitty }; //object 생성의 좋은 방식
//Cat kitty3 = kitty; //copy constructor 호출 (assignment랑 헷갈릴 수 있다)
Cat kitty3{ std::move(kitty) };
//---------------------------------------------------
//copy, move assignment 예시 코드
Cat kitty{ "kitty", 1 };
Cat nabi{ "nabi", 2 };
kitty = nabi; //copy assignment
kitty.print();
kitty = std::move(nabi); //move assignment
kitty.print();
nabi.print();
//kitty = kitty;
//kitty = std::move(kitty); //포인터 멤버 변수가 있을때 안정성에 좋지 못하다. -> self assignment 방지 코드 만들기
return 0;
}
- main 함수의 동작과 출력(copy, move constructor 예시 코드)
- Tip
- 클래스에서 포인터로 리소스 관리하는 경우 여러 메소드들에 대해 주의 깊게 살펴보고 구현해야 한다. -> 생산성이 낮아짐
- 포인터를 통한 리소스 관리는 좋지 않은 방식! (포인터가 아닌 경우 생성자만 만들어 주면 나머지는 스스로 생성)
- delete 키워드 :
Cat(const Cat& other) = delete;
-> 강제로 컴파일러가 copy constructor 만드는 것을 금지시킴
- static 메소드만 있는 클래스의 경우 오브젝트를 만들 필요 없다
-> 생성자를 delete로 만든다.
- singleton 패턴의 경우 copy constructor 를 막을 때 사용
- private 쪽에 메소드들을 넣어서 구현하기도 한다. (C++11 이전의 방법)
Operator Overloading
- function overloading : 함수 이름은 같지만 파라메터가 다를 때
- operator overloading 예시
#include<iostream>
#include<string>
class Cat
{
public:
Cat(std::string name, int age)
:mName{std::move(name)}
, mAge{age}
{
}
const std::string& name() const
{
return mName;
}
int age() const
{
return mAge;
}
void print(std::ostream& os)const
{
os << mName << " " << mAge << std::endl;
}
private:
std::string mName;
int mAge;
};
bool operator==(const Cat& c1, const Cat& c2)
{
if (c1.age() == c2.age() && c1.name() == c2.name())
return true;
return false;
}
bool operator<(const Cat& c1, const Cat& c2)
{
if (c1.age() < c2.age())
return true;
return false;
}
std::ostream& operator<<(std::ostream& os, const Cat& c)
{
return os << c.name() << " " << c.age();
}
int main()
{
Cat kitty{ "kitty",1 };
Cat nabi{ "nabi",2 };
kitty.print(std::cout);
nabi.print(std::cout);
if (kitty == nabi) // == overloading
std::cout << "same" << std::endl;
else
std::cout << "not same" << std::endl;
if (kitty < nabi) // < overloading
std::cout << "nabi is old" << std::endl;
else
std::cout << "kitty is old" << std::endl;
std::cout << kitty << std::endl; // << overloading
std::cout << nabi << std::endl;
return 0;
}
Class Keywords
- const :
-> 붙일 수 있는 곳 모든 곳에 붙이기
-> member fuction 이 member variable 을 바꾸지 않는다면 const
- mutable :
-> const를 무시한다 (왠만하면 사용하지 말기)
- explicit
-> 원하지 않는 형변환 방지
-> constuctor 에서 주로 사용
-> constructor가 argument 하나만 가진다면 explicit 써서 더욱 안전한 코드로 만들기
-> implicit conversion을 막기 위해서 사용
- static
- friend : oop의 컨셉이 망가질 수 있다.
- volatile : variable optimization 막음
- inline : 함수의 내용을 가져다가 함수 caller 부분에 넣어줌
- constexpr : 컴파일 시간에 const값들을 모두 값으로 지정
- 예시 코드
#include<iostream>
#include<string>
//for const and mutable
class Cat
{
public:
Cat(std::string name)
:mName(std::move(name))
{
}
void print() const
{
//mName = "no kitty"; 불가능!
mName2 = "what?"; //가능! by mutable
std::cout << mName << std::endl;
}
private:
std::string mName;
mutable std::string mName2;
};
//for explicit
class Dog
{
public:
explicit Dog(int age)
:mAge(age)
{
}
void printAge() const
{
std::cout << mAge << std::endl;
}
private:
int mAge;
};
int main()
{
//const and mutable 예시 코드
Cat kitty{ "kitty" };
kitty.print();
const Cat nabi{ "nabi" };
nabi.print(); //만약 print가 const 아니면 사용 불가능!!
//explicit 예시 코드
//Dog dog = 3; //explicit 없으면 허용되는 문법 (implicit conversion)
Dog dog{ 3 }; //ok
dog.printAge();
return 0;
}
- Encapsulation Tip
- return 값이 큰 경우 return by reference 방식을 사용하기!
- 예시 코드
class Widget
{
public:
void setInt(int a)
{
i = a;
}
//int는 return 값이 작음 -> return by value
int getInt()const
{
return i;
}
void setString(std::string a)
{
s = std::move(a);
}
//string 은 return 값이 큼 -> const reference로 return 하기!
std::string BadGetString() const // 1 copy
{
return s;
}
//앞의 const는 해당 값을 받은 reference가 받은 문자열을 수정할 수 없다는 의미
const std::string& GoodGetString() const // 0 copy
{
return s;
}
private:
int i;
std::string s;
};
int main()
{
Widget w;
w.setString("good");
std::string str1 = w.BadGetString(); //1 copy
const std::string& str2 = w.GoodGetString(); //0 copy
return 0;
}