생성자와 소멸자
생성자 (Constructor)
생성자는 객체가 생성될 때 자동으로 호출되는 특별한 멤버 함수입니다.
주로 객체의 초기화를 담당합니다.
기본 생성자
매개변수가 없는 생성자를 기본 생성자라고 합니다.
class MyClass {
public:
MyClass() {
std::cout << "Default constructor called" << std::endl;
}
};
int main() {
MyClass obj; // 기본 생성자 호출
return 0;
}
매개변수가 있는 생성자
객체 생성 시 초기값을 전달받아 초기화할 수 있습니다.
class Person {
private:
std::string name;
int age;
public:
Person(const std::string& n, int a) : name(n), age(a) {
std::cout << "Person created: " << name << ", " << age << std::endl;
}
};
int main() {
Person p("Alice", 30);
return 0;
}
복사 생성자
다른 객체를 이용하여 새로운 객체를 초기화할 때 사용됩니다.
class MyClass {
private:
int* data;
public:
MyClass(int value) : data(new int(value)) {}
// 복사 생성자
MyClass(const MyClass& other) : data(new int(*other.data)) {
std::cout << "Copy constructor called" << std::endl;
}
~MyClass() { delete data; }
};
int main() {
MyClass obj1(42);
MyClass obj2 = obj1; // 복사 생성자 호출
return 0;
}
이동 생성자 (C++ 11)
리소스의 소유권을 이전할 때 사용됩니다.
class MyClass {
private:
int* data;
public:
MyClass(int value) : data(new int(value)) {}
// 이동 생성자
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "Move constructor called" << std::endl;
}
~MyClass() { delete data; }
};
int main() {
MyClass obj1(42);
MyClass obj2 = std::move(obj1); // 이동 생성자 호출
return 0;
}
소멸자 (Destructor)
소멸자는 객체가 소멸될 때 자동으로 호출되는 특별한 멤버 함수입니다.
주로 동적으로 할당된 리소스를 해제하는 데 사용됩니다.
class MyClass {
private:
int* data;
public:
MyClass() : data(new int(0)) {}
~MyClass() {
delete data;
std::cout << "Destructor called" << std::endl;
}
};
int main() {
{
MyClass obj;
} // obj가 범위를 벗어나면 소멸자가 호출됩니다.
return 0;
}
생성자 초기화 리스트
생성자에서 멤버 변수를 초기화하는 효율적인 방법입니다.
class Rectangle {
private:
double width;
double height;
public:
Rectangle(double w, double h) : width(w), height(h) {
std::cout << "Rectangle created with width " << width
<< " and height " << height << std::endl;
}
};
위임 생성자 (C++ 11)
한 생성자가 같은 클래스의 다른 생성자를 호출할 수 있습니다.
class Person {
private:
std::string name;
int age;
public:
Person(const std::string& n, int a) : name(n), age(a) {}
Person() : Person("John Doe", 0) {} // 위임 생성자
Person(const std::string& n) : Person(n, 0) {} // 위임 생성자
};
명시적 생성자 (explicit)
암시적 형변환을 방지하기 위해 사용됩니다.
class MyClass {
public:
explicit MyClass(int x) {
std::cout << "MyClass created with " << x << std::endl;
}
};
int main() {
MyClass obj1(10); // OK
// MyClass obj2 = 20; // 컴파일 에러: 암시적 변환 불가
return 0;
}
초기화 순서
멤버 변수의 초기화 순서는 클래스에서 선언된 순서를 따릅니다, 초기화 리스트의 순서와는 무관합니다.
class MyClass {
private:
int x;
int y;
public:
MyClass(int a, int b) : y(b), x(a) { // x가 먼저 초기화됩니다.
std::cout << "x: " << x << ", y: " << y << std::endl;
}
};
가상 소멸자
상속 관계에서 기본 클래스의 소멸자를 가상으로 선언하여 적절한 소멸자 호출을 보장합니다.
class Base {
public:
virtual ~Base() {
std::cout << "Base destructor" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() override {
std::cout << "Derived destructor" << std::endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 두 소멸자 모두 호출됩니다.
return 0;
}
실습 : 메모리 관리 클래스 구현
다음 요구사항을 만족하는 메모리 관리 클래스를 구현해보세요.
- 동적으로 할당된 정수 배열을 관리
- 복사 생성자, 이동 생성자, 소멸자 구현
- 배열 크기 조정 기능 구현
#include <iostream>
#include <algorithm>
class DynamicArray {
private:
int* data;
size_t size;
public:
// 생성자
DynamicArray(size_t s = 0) : size(s), data(size ? new int[size]() : nullptr) {
std::cout << "Constructor called, size: " << size << std::endl;
}
// 소멸자
~DynamicArray() {
delete[] data;
std::cout << "Destructor called" << std::endl;
}
// 복사 생성자
DynamicArray(const DynamicArray& other) : size(other.size), data(size ? new int[size] : nullptr) {
std::copy(other.data, other.data + size, data);
std::cout << "Copy constructor called" << std::endl;
}
// 이동 생성자
DynamicArray(DynamicArray&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
std::cout << "Move constructor called" << std::endl;
}
// 배열 크기 조정 메서드
void resize(size_t newSize) {
int* newData = newSize ? new int[newSize]() : nullptr;
size_t copySize = std::min(size, newSize);
std::copy(data, data + copySize, newData);
delete[] data;
data = newData;
size = newSize;
}
// 접근자 메서드
int& operator[](size_t index) { return data[index]; }
const int& operator[](size_t index) const { return data[index]; }
size_t getSize() const { return size; }
};
int main() {
DynamicArray arr1(5);
for (size_t i = 0; i < arr1.getSize(); ++i) {
arr1[i] = i * 10;
}
DynamicArray arr2 = arr1; // 복사 생성자 호출
DynamicArray arr3 = std::move(arr1); // 이동 생성자 호출
arr2.resize(10); // 크기 조정
std::cout << "arr2 contents after resize:" << std::endl;
for (size_t i = 0; i < arr2.getSize(); ++i) {
std::cout << arr2[i] << " ";
}
std::cout << std::endl;
return 0;
}
연습 문제
String
클래스를 구현하세요. 이 클래스는 문자열을 동적으로 할당하고 관리해야 합니다.
복사 생성자, 이동 생성자, 소멸자를 구현하고, 문자열 연결 연산자(+)를 오버로딩하세요.
참고자료
- Stroustrup, Bjarne. "The C++ Programming Language (4th Edition)"
- Meyers, Scott. "Effective Modern C++ : 42 Specific Ways to Improve Your Use of C++ 11 and C++ 14"
- Sutter, Herb and Alexandrescu, Andrei. "C++ Coding Standards: 101 Rules, Guidelines, and Best Practices"
- C++ Reference : Constructors and member initializer lists
- ISO C++ Core Guidelines : C.41 : A constructor should create a fully initialized object