C++ OOP

LeemHyungJun·2022년 9월 16일
0

C++ Study

목록 보기
3/12
post-thumbnail

OOP Intro

  • Abstraction : class의 모델링 과정, 필요한 것만 보여주고 세부적인 정보는 몰라도 가능하게 하는 것
  • Encalsulation : 데이터 숨기기
  • Inheritance : 상속
  • Polymorphism : 같아 보이지만 다른 동작을 함
    • Function overloading -> static
    • Function overriding -> dynamic

Object Alignment

  • object가 메모리에 allocation 되는 동작 코드
    • 메모리 패딩
    1. 각 멤버변수들은 자신의 바이트 값의 배수에서 시작해야 함
    2. 해당 오브젝트의 메모리 크기는 가장 큰 멤버 변수의 배수로 끝남
    • 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;
}

0개의 댓글