Post

2026-03-11 TIL (12일차)

2026-03-11 TIL (12일차)

C++ 공부

3주차 과제 도전기능

1.friend 키워드 (공부)

friend로 함수를 만들게 되면 클래스 안에 포함되어 있으나 멤버 함수는 아니며 프렌드 함수의 본체는 외부에서 따로 정의된다.
즉, 이 말은 전역함수가 된다는 의미로 나는 받아들였다.
그래서 테스트를 위해 코드를 한번 작성해봤다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//class.h
void test();

class Test
{
public:
	Test();
	~Test();
private:
	friend void test();
};

//class.cpp
void test()
{
	std::cout << "Friend keyword" << std::endl;
}
Test::Test(){}
Test::~Test(){}

//main.cpp
int main() 
{
	Test A;
	test();
}

이렇게 되면 실행이 가능할 줄 알았다 전역함수니 어디에 불러도 상관없으니 하지만 오류가 발생했다.
과제 출력 에러
이유는 friend 선언만으로는 외부에서 해당 함수를 찾을 수 없어서이다.
그러므로 클래스 밖에서도 이 함수를 알 수 있도록 전역 선언을 추가를 해줘서 해결해줬다.

주의) 다른 클래스를 생성하고 전에 만든 friend 함수와 똑같이 선언하게되면 어떤 함수를 사용하는지 컴퓨터에서 모른다.(멤버 함수가 아니라서)

2.도전기능 Assign 함수 구현 메모리 릭 일어나는 부분 찾아보기

Assign(대입) 함수 구현 (void Assign(const Inventory<T>& other);)

  • 이미 만들어진 객체에 다른 객체의 내용을 덮어씁니다.
  • 기존에 가지고 있던 메모리는 delete[]로 먼저 비워준 뒤, 새로운 공간을 만들어 복사해야 메모리 누수가 생기지 않습니다.

위에 기능을 보던 중 그냥 복사생성자랑 같은거 아닌가 생각해서 복사생성자와 똑같이 구현을 해봤다.

1
2
3
4
5
6
7
    capacity_ = other.capacity_;
    size_ = other.size_;
    pItems_ = new T[capacity_];
    for (int i = 0; i < size_; ++i) {
        pItems_[i] = other.pItems_[i];
    }
    cout << "인벤토리 복사 완료" << endl;

하지만 구현 설명부분에 메모리 누수가 생기는지 확인을 해보았다.
메모리 누수 그림과 같이 Assign함수를 사용하기전과 후에 힙 메모리차이가 생겼다.
그럼 메모리 누수가 생기므로 문제가 생길 수도 있게된다.

복사생성자랑와 다르게 메모리 누수가 생기는 이유

  1. 복사 생성자가 불리는 타이밍은 객체가 생성되는 그 순간이여서 포인터는 손에 쥐고 있는 메모리가 하나도 없다.
  2. Assign함수는 이미 객체가 생성된 후 포인터가 메모리 주소를 가지고 있어서 해제후 재할당 해줘야한다.

3.인벤토리 자동 확장 (Resize) 오류 발생

Resize 멤버 함수 새로운 메모리 공간을 할당받는 함수 구현
Add함수를 호출 시 배열이 꽉 찼으면 기존 용량의 2배로 알아서 증가해야함

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	T* temp = new T[capacity_];
	for (int i = 0; i < size_; ++i)
	{
		temp[i] = this->pItems_[i];
	}

	delete[] this->pItems_;
	//복사한걸 다 옮기고 삭제

	this->pItems_ = new T[newCapacity];
	for (int i = 0; i < size_; ++i)
	{
		this->pItems_[i] = temp[i];
	}
	//복사한값들을 이제 새로 할당한 부분에 채워넣기

위에 코드처럼 함수를 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	Inventory<Item> test2(3);
	test2.AddItem(Item("Add", 10));
	test2.AddItem(Item("ndd", 30));
	test2.AddItem(Item("ddd", 30));
	test2.AddItem(Item("zdd", 30));
	test2.PrintAllItems();
	
	Inventory<Item> test3(test2);
	test3.PrintAllItems();
	
	Inventory<Item> test4;
	test4.AddItem(Item("bdd", 10));
	test4.AddItem(Item("zdd", 30));
	test4.Assign(test2);
	test4.PrintAllItems();

main함수에 위에 코드를 실행시 오류가 발생함

과제 출력 에러

오류를 검색해보니 메모리에 잘못된 접근을 하면 나타나는 오류
디버깅을 통해 알게된 경로가 복사생성자가 불리는 시점인걸 알게되었음

과제 출력 에러

찾아보니 Resize함수에 템플릿 클래스에 있는 Capacity값을 newCapacity으로 갱신을 안시켜줘서 발생
(공간을 할당을 받았는데 복사를 했을경우 Capacity값만큼 다시 할당받고 생성하는데 size는 계속 늘어나는데 Capacity값은 그대로여서 배열 크기를 넘어서 접근을 하게 됨)

Resize함수에 this->capacity_ = newCapacity; 추가

4.아이템 정렬 기능 (Sort) 구현

sort 선언법
sort(배열 시작 주소, 배열의 정렬할 마지막 인덱스, 정렬 함수);
sort(벡터 시작 iterator, 벡터 종료 iterator, 정렬 함수);

Item 클래스에 템플릿 클래스에서 사용하기 위해 연산자 오버로딩 “<” 구현

C++알고리즘 (vector)

vector는 연속 메모리에 데이터를 담는 동적배열

1.vector 멤버 변수

capacity: 벡터의 새로운 메모리 할당 없이 담을 수 있는 최대 원소 개수 size: 현재 벡터에 실제로 들어있는 원소의 개수

2.vector 재할당 메커니즘

capacity가 전부 찬 상태에서 새로운 원소가 들어오면, 더 큰 새로운 메모리 공간을 찾아 기존 원소들을 모두 복사(Copy/Move)하는 재할당 과정이 발생
재할당 비용: O(n) (기존 n개의 원소를 새 공간으로 복제)

하지만 벡터의 증가는 한칸씩 증가하는게 아니라 배수로 증가한다.

1칸씩 늘리는 경우
총 복사 횟수는 $1 + 2 + 3 + … + (n-1) = n(n-1)/2이 되어 시간 복잡도는 O(n^2)이 된다.
push_back 한 번의 평균 비용 O(n)되어 매우 비효율적

분할 상환 분석
2배씩 늘릴 경우, 재할당은 $1, 2, 4, 8, … 처럼 log n번만 일어납
총 복사 횟수: 1 + 2 + 4 + 8 + … + 2^k = 2n
n개를 넣는 데 총 2n번의 연산이 필요하므로, push_back 1회당 평균 복사 비용은 O(1)로 수렴

주의) 컴파일러마다 vector의 확장 비율이 다르다.

vector의 크기를 미리 안다면 reserve(n)을 통해 미리 메모리를 확보하고 재할당 횟수를 0으로 만들어 성능을 극대화할 수 있다.

3.vector 연산별 시간 복잡도

연산시간 복잡도왜?
push_back()평균 $O(1)$뒤에 추가만 하면 됨
pop_back()$O(1)$마지막 원소만 제거
v[i]$O(1)$연속 메모리, 바로 접근
insert(중간)$O(n)$뒤의 원소를 전부 밀어야 함
erase(중간)$O(n)$뒤의 원소를 전부 당겨야 함

4.vector 순회 방법 비교

방법코드언제?
인덱스 for문for (int i = 0; i < v.size(); i++)인덱스가 필요할 때
범위 기반 forfor (int x : v)읽기만 할 때
범위 기반 + 참조for (int& x : v)원소를 수정할 때

💡 요약

  • 읽기for (int x : v)
  • 수정for (int& x : v)
  • 인덱스 필요 ➜ 인덱스 for문

5.vector의 메모리 구조

vector 객체 자체는 스택에 저장되지만, 실제 데이터는 힙 영역에 연속된 메모리 형태로 저장됩니다.

스택(Stack): 함수 호출 시 자동으로 잡히는 작은 공간으로, vector 객체의 메타데이터(data 포인터, size, cap)가 저장됩니다.
힙(Heap): 프로그래머가 요청해서 잡는 넓은 공간으로, 실제 데이터들이 연속적으로 배치됩니다.

6.캐시 효율성: 캐시 히트 vs 캐시 미스

CPU는 메모리에서 데이터를 가져올 때 주변 데이터도 함께 가져오기 때문에, 데이터가 붙어있는 것이 성능에 유리합니다.

캐시 히트(Cache Hit): vector처럼 데이터가 연속적으로 붙어 있으면 한 번에 여러 원소를 가져오므로 속도가 매우 빠릅니다.
캐시 미스(Cache Miss): 데이터가 메모리 이곳저곳에 흩어져 있으면 매번 새로 가져와야 하므로 속도가 느려집니다.(linked list)

This post is licensed under CC BY 4.0 by the author.