묻공러
[C++ NOTE] OOP
묻공러
묻지마공부
묻공러
전체
오늘
어제
  • 분류 전체보기 (521) N
    • C (54)
      • [코드조선] C 핵심 (35)
      • [언어본색] C 기초 (19)
    • C++ (72)
      • [루키스] C++ (9)
      • [루키스] 콜백함수 (6)
      • [루키스] STL (8)
      • [루키스] Modern C++ (11)
      • [노코프] C++ (10)
      • [노코프] Tips (16)
      • [일지] C++ (12)
    • C# (20) N
      • [루키스] C# (9)
      • [루키스] 자료구조 (3)
      • [루키스] 실전 문법 (8) N
    • 자료구조 & 알고리즘 (50)
      • [코드조선] C 자료구조 & 알고리즘 (6)
      • [합격자되기] C++ 코딩테스트 (12)
      • [루키스] C++ 자료구조 & 알고리즘 (32)
    • CS (69)
      • [널널한 개발자] CS 개론 (19)
      • [혼자 공부하는] 컴퓨터 구조 (16)
      • [혼자 공부하는] 운영체제 (18)
      • [널널한 개발자] 네트워크 (16)
    • 게임 그래픽스 (46)
      • [전북대] OpenGL (25)
      • [일지] DirectX (21)
    • 게임 엔진 - 언리얼 (123)
      • [코드조선] 언리얼 (53)
      • [코드조선] 언리얼 데디서버 (8)
      • [일지] 언리얼 (59)
      • [일지] 언리얼 (2) (3)
    • 게임 엔진 - 유니티 (14)
      • [최적화] 유니티 (4)
      • [루키스] 유니티 (10)
    • 게임 서버 (17)
    • 게임 수학 & 물리 (19)
      • 게임 수학 (12)
      • 게임 물리 (7)
    • GIT & GITHUB (4)
    • 영어 (18)
      • [The Outfit] 대본 공부 (11)
      • the others (7)
    • 그 외 (14)
      • In (5)
      • Out (5)
      • Review (4)

인기 글

최근 글

hELLO · Designed By 정상우.
C++/[노코프] C++

[C++ NOTE] OOP

2024. 3. 27. 12:11

01. OOP Intro

# Multi paradigm
C++은 Multi paradigm 언어이다

그 종류는 아래와 같다

1) Procedural
2) Functional
3) OOP
4) generic

 

# Class와 Object
OOP에서
Class는 Definition이고
이러한 Class를 통해 Object가 생성된다

아래의 예시에서는

Cat이 Class이고

kitty가 Object이다

class Cat
{
public:
	void speak(int name);
	void speak(float weight);
private:
	int mAge;
};

int main()
{
	Cat kitty;
	kitty.speak();
	Cat nabi;
	nabi.speak();
}


# C++를 OOP중심의 언어로 만들어주는 4가지
1) Abstraction
객체의 공통적인 속성과 기능을 추출하여 정의하는 것을 추상화라고 한다
따라서

Animal Class를 abstract class라고 부르고
Cat Class를 interface라고 부른다

2) Encapsulation
외부에서 접근 가능하지 못하도록 private으로 막는 것을 의미한다

3) Inheritance
상속을 통해 상위(부모) 클래스와 하위(자식) 클래스 간의 관계가 발생한다

4) Polymorphism
1] function overloading
동일한 함수 이름을 가지고 인자가 다른 경우 

2] function overriding
override와 virtual 키워드를 통해
자식 클래스에서 함수를 오버로딩하는 경우

 



02. Object Alignment
# 멤버변수의 Alignment 규칙
1) 각 멤버변수는 본인 사이즈의 배수의 위치에 시작해야 함
2) 가장 큰 멤버변수의 사이즈의 배수로 끝이 나야 함

 

아래의 Cat Class는 

위의 규칙에 따라 총 24바이트를 차지한다

class Cat 
{
public:
	void speak();
private:
	char c1;  //1 byte
	int i4a;  //4 bytes
	int i4b;  //4 bytes
	double d8;//8 bytes
};

 

# alignas
캐시라인은 일반적으로 64바이트이다
만약에,

사이즈가 큰 클래스 배열인 경우에는
캐시라인에 맞춰서 배열이 잘리다 보니

클래스의 데이터가 불균일하게 조각화가 되어
False Sharing 문제가 발생할 수 있다

 

따라서,
alignas(32)를 통해 클래스의 사이즈를 32로 만들어주면

캐시라인에 맞춰져서 False Sharing 문제가 발생하지 않는다

#include <iostream>
class alignas(32) Cat 
{
public:
	void speak();
private:
	char c1;  //1 byte
	int i4a;  //4 bytes
	int i4b;  //4 bytes
	double d8;//8 bytes
};


int main()
{
	Cat cats[100];
	//prefer std::array

	return 0;
}

 




03. Static Members

# Static의 3가지

1) static variable in a function

2) static member function

3) static member variable

 

# static member function

class static member function은

오브젝트의 주소를 가리키는 this와 바인딩되어있지 않다

따라서,

오브젝트를 만들지 않아도 호출이 가능하고

오브젝트와 전혀 연관이 없다

this가 없기 때문에 static member function 내에서

멤버 변수 및 멤버 함수를 사용할 수 없다

 

static member function도 결국 멤버함수이기에

오브젝트에서 접근이 가능하고

오브젝트 없이도 접근이 가능하다

#include <iostream>
class Cat
{
public:
	static void speak()
	{
		//mAge = 1;// 불가능
		std::cout << "meow" << std::endl;
	};

private:
	int mAge;
};


int main()
{
	Cat::speak();// 객체 생성 없이

	Cat kitty;
	kitty.speak();// 객체 생성 후 멤버함수 호출

	return 0;
}

 

# static member variable

static member variable은 프로그램이 실행되기 전에 반드시 초기화를 해줘야 한다

static member variable은 모든 오브젝트들이 static 메모리에서 하나의 공간을 공유한다 

static member variable이 함수 내에서만 사용이 된다면,

멤버 변수로 가지지 않고 함수 내에 선언하면 초기화 및 안전성에서 좋다

const 키워드나 constexpr 키워드와 사용할 때는

선언과 초기화를 동시에 할 수 있다

#include <iostream>
class Cat
{
public:
	void speak()
	{
		static int count = 0;// 함수 내에서만 사용된다면 이렇게 바로 선언 및 초기화
		count++;
		std::cout << count << "meow" << std::endl;
	};

private:
	int mAge;
	//static int count;
	const static int a = 0;// const 키워드를 사용하면, 선언과 초기화를 동시에 가능
	constexpr static int b = 0;// constexpr 키워드를 사용하면, 선언과 초기화를 동시에 가능
};

//int Cat::count = 0;// 반드시 외부에서 초기화가 필요

int main()
{
	Cat kitty;
	Cat nabi;
	kitty.speak();
	nabi.speak();

	return 0;
}

 



04. Member Init List

초기화를 생성자와 소멸자 구현 부분에서 하게 되면,

멤버 변수를 생성하고 재할당을 하게 된다

반면, Member Initializer List를 사용하면

멤버 변수를 생성없이 바로 초기화 하기에 메모리 낭비가 없다

#include <iostream>
class Cat
{
public:
	Cat() :mAge(1) {};
	Cat(int age) :mAge(age) {};
private:
	int mAge;

};
class Zoo
{
public:
	Zoo(int kittyAge) :mKitty(Cat(kittyAge))
	{
		//mKitty = Cat(kittyAge);
		// 임시 오브젝트가 생성
	};
private:
	Cat mKitty;
};


int main()
{
	Zoo cppZoo(5);
	return 0;
}

 



05. Copy/Move Constructor

# 자동으로 생성되는 함수
클래스에서 자동으로 만들어주는 함수는 아래와 같다

1) Constructor

2) Destructor

3) Copy/Move Constructor

4) Copy/Move Assignment

 

# 기본 Constructor가 자동으로 생성되지 않는 경우

인자를 받는 Constructor를 우리가 직접 만들어준다면,

기본 Constructor가 자동으로 만들어지지 않는다

따라서 기본 Constructor를 반드시 직접 작성해야 하고 default 문법을 이용하는 것이 좋다

#include <iostream>
#include <string>
class Cat
{
public:
	Cat() = default;
	Cat(std::string name, int age) :mName{ std::move(name) }, mAge{ age }
	{
		std::cout << mName << " constructor" << std::endl;
	};

	void print()
	{
		std::cout << mName << " " << mAge << std::endl;
	};
private:
	std::string mName;
	int mAge;
};

int main()
{
	Cat a;// default를 해주었기 때문에 가능
	Cat kitty{ "kitty",1 };
	Cat nabi{ "nabi",2 };

	kitty = nabi; //copy assignment
	kitty = std::move(nabi); //move

	return 0;
}

 

# 멤버 변수에 포인터

만약 멤버 변수에 포인터형이 있다면

(클래스 내부에서 포인터를 통해 리소스 관리를 Heap에서 하는 경우)

Destructor

, Copy/Move Constructor

, Copy/Move Assignment를 직접 선언해줘야 한다

 

- Destructor

Constructor가 오브젝트가 생성될 때, 가장 먼저 호출이 되는 것처럼

Destructor는 오브젝트가 소멸될 때, 가장 마지막에 호출이 된다

 

- Copy Constructor

기존 오브젝트를 Copy 해서 새로운 오브젝트를 만들 때 사용된다

 

- Move Constructor

기존 오브젝트를 Move 해서 새로운 오브젝트를 만들 때 사용된다

당연히 기존 오브젝트의 소유권은 모두 사라진다

 

#include <iostream>
#include <string>
class Cat
{
public:
	Cat() = default;

	Cat(std::string name, int age) :mName{ std::move(name) }, mAge{ age }
	{
		std::cout << mName << " constructor" << std::endl;
	};

	~Cat()
	{
		std::cout << mName << " destructor" << std::endl;
	}

	Cat(const Cat& other)
		: mName{ other.mName }
		, mAge{ other.mAge }
	{
		std::cout << mName << " copy constructor" << std::endl;
		// 멤버 변수에 포인터가 있다면,
		// std::memcpy()를 이용해서 리소스 전체를 복사
	}

	Cat(Cat&& other)
		: mName{ std::move(other.mName) }
		, mAge{ other.mAge }
	{
		std::cout << mName << " move constructor" << std::endl;
		// 멤버 변수에 포인터가 있다면,
		// mPtr = other.mPtr;
		// other.mPtr = nullptr;
	}

	void print()
	{
		std::cout << mName << " " << mAge << std::endl;
	};
private:
	std::string mName;
	int mAge;
	// char * mPtr;
};

int main()
{
	Cat kitty{ "kitty",1 };
	Cat nabi{ "nabi",2 };

	Cat kitty2{ kitty };// copy constructor 1번 방식
	Cat kitty3 = kitty;// copy constructor 2번 방식

	Cat kitty4{ std::move(kitty) };

	return 0;
}

 



06. Copy/Move Assignment
# Copy Assignment와 Move Assignment

- Copy Assignment

하나의 오브젝트에 다른 오브젝트를 대입할 때, 복사가 되며 불러지는 연산자이다

 

- Move Assignment

하나의 오브젝트에 다른 오브젝트(R-value)를 대입할 때, 이동이 되며 불러지는 연산자이다

 

#include <iostream>
#include <string>
class Cat
{
public:
	Cat() = default;

	Cat(std::string name, int age) :mName{ std::move(name) }, mAge{ age }
	{
		std::cout << mName << " constructor" << std::endl;
	};

	~Cat() noexcept
	{
		std::cout << mName << " destructor" << std::endl;
	}

	Cat(const Cat& other)
		: mName{ other.mName }
		, mAge{ other.mAge }
	{
		std::cout << mName << " copy constructor" << std::endl;
		// 멤버 변수에 포인터가 있다면,
		// std::memcpy()를 이용해서 리소스 전체를 복사
	}

	Cat(Cat&& other) noexcept
		: mName{ std::move(other.mName) }
		, mAge{ other.mAge }
	{
		std::cout << mName << " move constructor" << std::endl;
		// 멤버 변수에 포인터가 있다면,
		// mPtr = other.mPtr;
		// other.mPtr = nullptr;
	}

	Cat& operator=(const Cat& other)
	{
		// self assignment를 하게되면, 
		// 멤버 변수에 포인터가 있을 시 예기치 못한 오류가 발생할 수 있기에 작성
		if (&other == this)
			return *this;

		mName = other.mName;
		mAge = other.mAge;
		std::cout << mName << " copy assignment" << std::endl;
		return *this;
	}

	Cat& operator=(Cat&& other) noexcept
	{
		if (&other == this)
			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;
	// char * mPtr;
};

int main()
{
	Cat kitty{ "kitty",1 };
	Cat nabi{ "nabi",2 };

	kitty = kitty;// self assignment
	kitty = std::move(kitty);// self assignment

	kitty = nabi; // copy assignment
	kitty = std::move(nabi); // move assignment

	return 0;
}

 

# noexcept

Destructor, Move Constructor, Move Assignment는

새로운 리소스를 요청하지 않고

exception이 throw 되지 않기에 noexcept를 작성해줘야 한다

그러면 추후 컴파일러가 move가 필요할 때 noexcept라는 것을 인지하고

확실하게 move를 호출해 주게 된다

 

# delete

특정 함수를 막으려면 delete를 사용하면 된다

C++ 11 이전에는 delete 기능이 없어서

private을 활용했다

class Cat
{
public:
	Cat(const Car& other) = delete;// delete 이용
    
private:
	Cat();// C++ 11 이전
	std::string mName;
	int mAge;
};

 



07. Operator Overloading
# Function Overloading

함수의 이름은 같은데

인자의 개수 및 종류가 다른 함수들을 생성하는 것을 의미한다

static polymorphism이라고 불리는데

어떤 함수가 바인딩이 될지 컴파일 시 결정이 되기 때문이다

반면, dynamic polymorphism은

어떤 함수가 바인딩이 될지 런타임시 결정이 된다

 

# Operator Overloading

연산자를 직접 오버로딩하는 것을 의미하고

*, %, /, +=, new, delete, [], ()와 같은 다양한 연산자가 지원된다

 

특히, Operator Overloading은 STL와 함께

지원이 되며 유용하다

예를 들어 비교 연산자를 오버로딩하면

그 연산자를 기반으로 std::sort()가 작동한다

 

아래는

outstream 연산자인 "<<"를 직접 작성해 준 것이다

#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;
};

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};

	std::cout << kitty << std::endl;
	std::cout << nabi << std::endl;
	// ==, < , <<

	// kitty.print(std::cout);
	// nabi.print(std::cout);
	return 0;

}

 

 



08. Class Keywords

# const

멤버 함수 내부에서

멤버 변수의 값을 바꾸지 않는다면

해당 멤버 함수 뒤에 const를 무조건 붙이는 것이 좋다

그 이유는

컴파일러가 const를 확인하고

const 멤버 함수 구현부에서 멤버 변수를 바꾼다면

컴파일 에러로 리포팅되기 때문에 더욱 안전한 개발이 가능하기 때문이다

 

또한,

클래스를 const로 생성했다면

해당 클래스는 const 멤버 함수만 호출이 가능하고

일반적인 멤버 함수는 호출이 불가능하며 에러가 발생한다

 

mutable이 붙은 멤버 변수는

const가 붙은 멤버 함수에서 마음대로 변경이 가능하다

그렇지만 const를 사용하는 이유가 없어지기에

mutable은 가급적 사용을 하지 않는 것이 좋다

 

#include <iostream>
#include <string>
class Cat
{
public:
	void age(int age)
	{
		mAge = age;
	}
	int age() const
	{
		// mAge = 100;// 에러 발생
		mHp = 100;// mutable 변수는 const가 붙은 함수에서도 변경 가능
		return mAge;
	}
	void initAge()
	{
		mAge = 100;
	}

private:
	int mAge;
	std::string mName;
	mutable int mHp;
};


int main()
{
	const Cat kitty{ "kitty" };
	kitty.age();
	//kitty.initAge();// 호출 불가

	return 0;
}

 

# explicit

constructor에서 implicit conversion이 일어나는 것이 기본적이다

implicit conversion을 막기 위해서는

explicit 키워드를 이용하면 된다

특히,

constructor의 argument를 하나일 때는

implicit conversion을 막기 위해서 explicit 키워드를 반드시 작성하는 것이 좋다

 

#include <iostream>
#include <string>
class Cat
{
public:
	explicit Cat(int age)
		: mAge(age)
	{

	};

private:
	int mAge;
};


int main()
{
	//Cat kitty = 3;// implicit conversion을 막는 explicit 키워드로 인해 에러
	Cat kitty{3};

	return 0;
}

 

# friend

private 함수를 호출할 수 있는 클래스를 지정하는 키워드이다 

OOP의 콘셉트를 파괴할 수 있기 때문에 가급적 사용을 피하는 것이 좋다

 

# volatile

variable optimization을 꺼주는 키워드이다

 

# inline

함수의 내용을 그대로 가져다가 함수 caller 부분에 넣는 키워드이다

 

# constexpr

컴파일 시간에 해당 키워드 변수들을 모두 값으로 지정한다

(반면, const는 컴파일 or 런타임 시간에 지정한다)

 

# Encapsulation Interface

값이 작을 때는 return by value를 해도 되지만

값이 클 때는 return by const ref가 더 효율적이다

멤버 함수 앞에 const를 붙이는 것은

해당 반환값을 수정하지 못하도록 하는 Encapsulation Inteface에서

사용한다

#include <iostream>
#include <string>
class Cat
{
public:
	int age() const// 값이 작은 경우 return by value
	{
		return mAge;
	}
	const std::string& name() const// 값이 큰 경우 return by const ref
	{
		return mName;
	}

private:
	int mAge;
	std::string mName;

};


int main()
{
	const Cat kitty{ "kitty" };

	std::string name = kitty.name();// deep copy
	const std::string& nameRef = kitty.name();//no deep copy

	return 0;
}

 

저작자표시 비영리 변경금지 (새창열림)

'C++ > [노코프] C++' 카테고리의 다른 글

[C++ NOTE] Smart Pointer  (0) 2024.03.29
[C++ NOTE] Inheritance  (0) 2024.03.28
[C++ NOTE] Resource Move  (0) 2024.03.26
[C++ NOTE] Compile Process  (0) 2024.03.25
[C++ NOTE] Memory Structure  (0) 2024.03.24
'C++/[노코프] C++' 카테고리의 다른 글
  • [C++ NOTE] Smart Pointer
  • [C++ NOTE] Inheritance
  • [C++ NOTE] Resource Move
  • [C++ NOTE] Compile Process
묻공러
묻공러
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.