C++ 소멸자, Stream

윤준혁·2025년 3월 3일

소멸자(Destructor)

소멸자란?

  • 클래스의 객체가 소멸될 때 자동으로 호출되는 특수한 멤버 함수
  • 일반적으로 객체가 스코프를 벗어나거나 delete 연산자를 통해 메모리에서 해제될 때 호출된다
#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass() { cout << "생성자 호출!" << endl; }
    ~MyClass() { cout << "소멸자 호출!" << endl; }
};

int main() {
    MyClass obj; // 객체 생성
    return 0;    // 객체가 스코프를 벗어나면서 소멸자 호출
}

출력 예)
생성자 호출!
소멸자 호출!

#include <iostream>
using namespace std;

class MyClass {
public:
    MyClass() { cout << "생성자 호출!" << endl; }
    ~MyClass() { cout << "소멸자 호출!" << endl; }
};

int main() {
    MyClass* obj = new MyClass(); // 동적 할당
    delete obj; // 명시적으로 delete 해야 소멸자가 호출됨
    return 0;
}

출력 예)
생성자 호출!
소멸자 호출!

특징

  • 소멸자의 이름은 클래스 이름 앞에 ~를 붙여서 정의
  • 매개변수를 가질 수 없으며, 오버로딩이 불가능하다
  • 객체가 소멸될 때 자동으로 호출되며, 리소스 정리 목적으로 사용된다

가상 소멸자(Virtual Destructor)

  • C++에서 기본 클래스의 포인터를 통해 파생 클래스의 객체를 삭제할 때, 올바른 소멸자를 호출하기 위해서는 가상 소멸자가 필요
  • 가상 소멸자를 사용하지 않으면 기본 클래스의 소멸자만 호출되어, 파생 클래스의 소멸자가 호출되지 않아 리소스가 정리되지 않는 문제가 발생할 수 있다
#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base 생성자 호출" << endl; }
    ~Base() { cout << "Base 소멸자 호출" << endl; } // 가상 소멸자가 아님
};

class Derived : public Base {
public:
    Derived() { cout << "Derived 생성자 호출" << endl; }
    ~Derived() { cout << "Derived 소멸자 호출" << endl; }
};

int main() {
    Base* obj = new Derived(); // 업캐스팅
    delete obj; // Base의 소멸자만 호출됨!
    return 0;
}

출력 예) - Derived 클래스의 소멸자가 호출되지 않아 메모리 누수를 일으킬 수 있다
Base 생성자 호출
Derived 생성자 호출
Base 소멸자 호출

해결 방법

  • 기본 클래스의 소멸자를 virtual 키워드를 사용하여 가상 소멸자로 선언하면, 올바르게 파생 클래스의 소멸자가 호출된다
#include <iostream>
using namespace std;

class Base {
public:
    Base() { cout << "Base 생성자 호출" << endl; }
    virtual ~Base() { cout << "Base 소멸자 호출" << endl; } // 가상 소멸자
};

class Derived : public Base {
public:
    Derived() { cout << "Derived 생성자 호출" << endl; }
    ~Derived() { cout << "Derived 소멸자 호출" << endl; }
};

int main() {
    Base* obj = new Derived(); // 업캐스팅
    delete obj; // 모든 소멸자가 올바르게 호출됨
    return 0;
}

출력 예)
Base 생성자 호출
Derived 생성자 호출
Derived 소멸자 호출
Base 소멸자 호출

순수 가상 소멸자(Pure Virtual Destructor)

  • 추상 클래스에서 순수 가상 소멸자를 선언할 수도 있다
  • 순수 가상 소멸자가 있더라도 반드시 구현이 필요
class AbstractBase {
public:
    virtual ~AbstractBase() = 0; // 순수 가상 소멸자
};

AbstractBase::~AbstractBase() { // 정의 필수!
    cout << "AbstractBase 소멸자 호출" << endl;
}

Stream

Stream이란?

  • C++에서 Stream은 데이터의 흐름을 의미하며, 입력과 출력을 처리하는 중요한 개념
  • 파일을 읽고 쓰거나 데이터의 형식을 조정하는 다양한 기능을 제공하며, 이를 통해 효율적인 입출력 작업을 수행할 수 있다
#include <iostream>
using namespace std;

int main() {
    string name;
    cout << "이름을 입력하세요: ";
    cin >> name;
    cout << "안녕하세요, " << name << "!" << endl;
    return 0;
}

주요 클래스

  • istream : 입력 스트림 클래스 (키보드 입력 등)
  • ostream : 출력 스트림 클래스 (콘솔 출력 등)
  • ifstream : 파일 입력 스트림 (파일에서 데이터 읽기)
  • ofstream : 파일 출력 스트림 (파일에 데이터 쓰기)
  • fstream : 파일 입출력 스트림 (파일을 읽고 쓰기 모두 가능)

파일에 쓰기(Writing in a File)

  • ofstream 클래스를 사용하여 파일에 데이터를 쓸 수 있다
#include <iostream>
#include <fstream>
using namespace std;

int main() {
    ofstream file("example.txt"); // 파일 열기
    if (file.is_open()) {
        file << "Hello, World!\n";
        file << "C++ File Writing Example.";
        file.close(); // 파일 닫기
        cout << "파일이 성공적으로 작성되었습니다." << endl;
    } else {
        cout << "파일을 열 수 없습니다." << endl;
    }
    return 0;
}

모드

  • ofstream을 사용할 때 특정 모드를 지정할 수 있다

    ios::out : 기본 쓰기 모드 (기존 내용 덮어쓰기)
    ios::app : 기존 파일 끝에 데이터 추가 (Append)
    ios::trunc : 파일 내용을 지우고 새로 작성

파일에서 읽기(Reading from a File)

  • ifstream 클래스를 사용하여 파일에서 데이터를 읽을 수 있다
#include <iostream>
#include <fstream>
using namespace std;

int main() {
    ifstream file("example.txt"); // 파일 열기
    if (file.is_open()) {
        string line;
        while (getline(file, line)) { // 한 줄씩 읽기
            cout << line << endl;
        }
        file.close(); // 파일 닫기
    } else {
        cout << "파일을 열 수 없습니다." << endl;
    }
    return 0;
}
ifstream file("example.txt");
string word;
while (file >> word) { // 공백을 기준으로 단어 읽기
    cout << word << endl;
}

직렬화(Serialization)

  • 객체 데이터를 파일에 저장하고, 다시 불러오는 과정을 의미
  • fstream을 사용하여 바이너리 데이터를 저장할 수 있다
#include <iostream>
#include <fstream>
using namespace std;

class Person {
public:
    string name;
    int age;

    void saveToFile() {
        ofstream file("person.dat", ios::binary);
        file.write(reinterpret_cast<char*>(this), sizeof(Person));
        file.close();
    }
};

int main() {
    Person p = {"Alice", 25};
    p.saveToFile();
    cout << "객체가 파일에 저장되었습니다." << endl;
    return 0;
}
Person p;
ifstream file("person.dat", ios::binary);
file.read(reinterpret_cast<char*>(&p), sizeof(Person));
file.close();

텍스트 vs 바이너리 (Text and Binary Files)

  • 텍스트 파일 : 사람이 읽을 수 있는 데이터 형식(.txt, .csv 등)
  • 바이너리 파일 : 이진 형식으로 저장된 파일(.dat, .bin 등)
ofstream file("data.bin", ios::binary);
int number = 123;
file.write(reinterpret_cast<char*>(&number), sizeof(int));
file.close();

조작자(Manipulators)

  • 입출력 스트림에서는 데이터를 포맷팅할 수 있도록 다양한 조작자를 제공

    setw(n) : 출력 폭 설정
    setprecision(n) : 소수점 자리수 설정
    fixed : 고정 소수점 형식
    scientific : 지수 표기법 사용
    left / right : 정렬 방식 설정

#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    double pi = 3.1415926535;
    cout << "기본 출력: " << pi << endl;
    cout << "소수점 3자리: " << fixed << setprecision(3) << pi << endl;
    cout << "과학적 표기법: " << scientific << pi << endl;
    return 0;
}

출력 예)
기본 출력: 3.14159
소수점 3자리: 3.142
과학적 표기법: 3.142e+00

0개의 댓글