'컴퓨터 언어/C++'에 해당되는 글 10건

  1. 2012.12.28 파일 입출력 by 똘이 임덕규
  2. 2012.12.24 예외 처리, 예외에 안전한 코드 만들기 by 똘이 임덕규
  3. 2012.12.24 예외 처리, 함수 깊숙히 by 똘이 임덕규
  4. 2012.12.21 예외 처리 by 똘이 임덕규
  5. 2012.12.20 오버라이딩 by 똘이 임덕규
  6. 2012.12.20 가상함수를 이용한 다형성 구현 by 똘이 임덕규
  7. 2012.12.20 다중상속(Multiplex inheritance) by 똘이 임덕규
  8. 2012.12.20 접근제어 키워드 by 똘이 임덕규
  9. 2012.12.20 포인터간의 형 변환, 레퍼런스간의 형변환 by 똘이 임덕규
  10. 2012.12.20 레퍼런스 by 똘이 임덕규

기본 입출력 코드

#include <iostream>
#include <fstream>              // 파일 스트림 헤더

using namespace std;

int main()
{
    ofstream emb;    // 파일 객체생성
	
    emb.open("text.txt", ios_base::out | ios_base::app);
    if(false == emb.is_open())  // 파일 성공적으로 열었는지 검사
    {
        cout << "Failed to open the file." << endl;
    }
	
    emb << "hi" << endl;

    return 0;
}

생성자나 open() 멤버 함수를 사용할 수 있는데 파일 이름만 제공하며 출력, 혹은 입력용으로 파일을 생성한다. 여기에 좀 더 구체적인 옵션을 넣을 수 있다.

의미
app 파일의 끝에 자료를 추가하기 위한 용도로 연다.
ate 파일을 열고 파일의 끝으로 이동한다.
binary 텍스트가 아닌 바이너리로 입출력을 한다.
in 파일에서 값을 읽기 위한 용도로 연다.
out 파일에 값을 쓰기 위한 용도로 연다.
trunc 기존 파일이 있다면 지워버리고 새 파일을 연다.
  • ofstream의 멤버함수 is_open() 은 파일을 제대로 열었는지의 여부를 bool 타입으로 반환값을 돌려준다.
  • 파일을 다 사용한 후에는 close() 함수를 사용해서 닫아줘도 되지만 닫아주지 않아도 객체가 소멸시 소멸자에서 닫아주기 때문에 닫지 않아도 무방하다.
저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

파일 입출력  (0) 2012.12.28
예외 처리, 예외에 안전한 코드 만들기  (0) 2012.12.24
예외 처리, 함수 깊숙히  (0) 2012.12.24
예외 처리  (0) 2012.12.21
오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20

리소스를 정리하기 전에 예외가 발생한 경우

동적할당등을 받은 메모리가 아직 해제가 안된 상태에서 예외처리에 의한 함수 종료는 심각한 메모리 누수 현상을 가져올 수 있다.

#include 

using namespace std;

void A();
void B();
void C();

int main()
{
    try
    {
        A();
    }
    catch(int ex)
    {
        cout << "예외 처리" << endl;
    }
}

void A()
{
    // 메모리를 할당
    char* p = new char [100];   // 메모리를 100바이트 할당

    // 여기까지 실행
    cout << "예외가 발생하기 전" << endl;
    B();

    // 이곳은 실행하지 않음을 출력
    cout << "예외가 발생한 후" << endl;
    delete[] p;
    p = NULL;
}

void B()
{
    throw "Exception!!";
}
  • 위 소스는 A() 함수의 앞에서 할당된 메모리가 해제되지 않는 문제가 발생.
  • 함수 끝에서 무언가 정리 작업을 해주는 경우가 있다면 모두 이 문제점에 노출

소멸자로 리소스 문제 해결하기

  • 함수 소멸시 함수안의 모든 변수 및 객체가 소멸
  • 객체는  소멸시 소멸자를 호출하는 방법을 이용
  • 이점을 이용, 리소스를 해제하는 용도의 클래스를 제작
  • 이것을 스마트 포인터라 부른다.
#include 

using namespace std;

class SmartPointer
{
public:
    SmartPointer(char* p)
        :ptr(p)
        {
            
        }
    ~SmartPointer()
        {
            // 소멸자가 호출되는 것을 확인한다.
            cout << "메모리가 해제된다." << endl;

            delete[] ptr;
        }
public:
    char* const ptr;
};

void A();
void B();

int main()
{
    try
    {
        A();
    }
    catch(const char* ex)
    {
        cout << "예외 처리" << endl;
    }

    return 0;
}

void A()
{
    // 메모리를 할당
    char* p = new char [100];   // 메모리를 100바이트 할당

    // 메모리를 스마트 포인터로 보관한다
    SmartPointer sp(p);

    // 예외를 던지는 함수 호출
    B();

    // 이곳은 실행하지 않음을 출력
    cout << "예외가 발생한 후" << endl;
    
   
    // delete[] p;
    // p = NULL;
}

void B()
{
    throw "Exception!!";
}
  • 함수가 정상적으로, 또는 예외에 의해서 종료된 경우 모두 스마트 포인터 객체 sp의 소멸자를 호출한다.
  • 소멸자단에서 메모리 해제등을 시켜주기 때문에 따로 신경쓰지 않아도 되게 된다.

주의 해야 할 점은 다음과 같다.

  • 생성자가 올바르게 종료된 경우만 객체를 생성한 것으로 간주한다.
  • 생성자에서 예외가 발생한 경우라면 정상적으로 종료된게 않은 것이고  객체도 생성되지 않은 것이다.
  • 객체가 생성되지 않았으니 소멸자도 호출될 일이 없다.
  • 결국 생성자단에서 일어난 메모리 누수를 해결하기 위한 예외를 다시 던져야 한다.

소멸자에서의 예외는 반드시 막아야 한다.

  • 객체의 소멸자에서 예외가 던져지는 경우 프로그램이 비정상 종료할 수 있다.
  • 소멸자 밖으로는 예외가 던져지지 않게 막아야 한다.
  • 생성자에서 했던 것처럼 소멸자의 모든 코드를 try블록으로 감쌀 필요가 있다.
  • 잡아낸 예외는 절대로 소멸자 밖으로 다시 던져서는 안 된다

저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

파일 입출력  (0) 2012.12.28
예외 처리, 예외에 안전한 코드 만들기  (0) 2012.12.24
예외 처리, 함수 깊숙히  (0) 2012.12.24
예외 처리  (0) 2012.12.21
오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20

구조적 예외 처리와 관련된 규칙

예외는 함수를 여러 개 건너서도 전달이 가능하다.

#include <iostream>

using namespace std;

void A();
void B();
void C();

int main()
{
    try
    {
        A();
    }
    catch(int ex)
    {
        cout << "예외 = " << ex << endl;
    }
}

void A()
{
    B();
}

void B()
{
    C();
}

void C()
{
    throw 337;
}
  • 보는 것과 같이 함수에 아무리 깊게 들어가게 되더라도 던져진 예외는 즉시 Catch가 받아서 처리
  • 예외는 자신의 타입에 맞는 catch블록을 찾을 때까지 함수를 거슬러 올라간다.
  • main()함수까지 갔는데도 알맞은 catch블록을 못 찾았다면 프로그램이 비정상 종료
저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

파일 입출력  (0) 2012.12.28
예외 처리, 예외에 안전한 코드 만들기  (0) 2012.12.24
예외 처리, 함수 깊숙히  (0) 2012.12.24
예외 처리  (0) 2012.12.21
오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20

왜 예외처리가 필요한가?

대표적인 예외처리는 아래와 같다.
  • 컴퓨터에 사용 가능한 메모리가 부족한 경우
  • 하드디스크에 파일을 쓰는데 하드디스크의 남을 용량이 부족한 경우
  • 사용자가 범위 밖의 값을 입력하거나 존재하지 않는 파일의 이름을 입력한 경우

여기서 중요한것은 버가느 오류같이 개발자의 실수로 일어나는 문제점들과 예외를 구별할 줄 알아야 한다.

DynamicArray.h 보기

DynamicArray.cpp 보기

main.c

반환 값을 사용한 예외처리

DynamicArray 클래스의 SetAt(), GetAt() 함수에는 문제점이 있다. SetAt()함수에는 원소의 인덱스를 넘겨주게 되어 있는데 배열의 크기를 넘겨서 인덱스를 넘겨주는 경우에는 할당 되지 않는 메모리를 건드려서 프로그램이 비정상 종료할 수 있다.
DynamicArray.cpp
bool void DynamicArray::SetAt(int index, int value)
{
    if(index < 0 || index >= GetSize())
    {
        return false;
    }

    arr[index] = value;

    return true;
}
example.cpp
#include "DynamicArray.h"
#include <iostream>
using namespace std;

int main()
{
    // 크기가 10인 배열 객체를 만든다.
    DynamicArray arr(10);

    // 올바른 인덱스를 참조한다
    bool b;
    b = arr.SetAt(5, 0);
    if(false == b)
    {
        cout << "arr[5]  사용실패!!" << endl;
    }

    // 각 원소에 10, 20, 30.. 순으로
    for(int i = 0; i < 10; ++i)
    {
        arr.SetAt(i, (i + 1) * 10);
    }

    // 배열의 크기를 출력하고, 각 원소의 값을 역순으로 출력
    cout << "Size of arr = " << arr.GetSize() << endl;

    for(i = 9; i >= 0; --i)
    {
        cout << "arr[" << i << "] = " << arr.GetAt(i) << endl;
    }

    return 0;
}
여기서 SetAt() 함수에 잘못된 인덱스를 넘긴 사실은 '예외 상황'이 되고, SetAt() 함수에서 이를 감지한 후에 예외 상황을 외부에 알리는 과정이 '예외 처리'라고 볼 수 있다.

반환 값을 사용한 예외처리의 문제점
  • 함수를 호출할 때마다 매번 반환 값을 비교하는 번거러움
  • 이미 함수가 다른 용도의 반환 값을 사용하는 경우
  • 본연의 소스 코드와 예외 처리 코드가 섞여 지저분 해지고 가독성이 떨어진다.

구조적 예외 처리

구조적 예외 처리를 위한 세가지 키워드
  • try
    • 예외가 발생하는지 지켜보는 키워드
    • 일종의 if문과 비슷하다.
  • catch
    • 예외를 잡아서 처리하는 키워드
  • throw
    • 예외 발생시, 예외를 지켜라고 지시하는 키워드
#include <iostream>

using namespace std;

int main()
{
    try
    {
        cout << "Try 1" << endl;
        
        // throw 99;               // 이곳에서 점프하게 된다.
        throw 99.1f;               // 이곳에서 점프하게 된다.
        
        cout << "Try 2" << endl;
    }

    catch(int n)    // throw가 int 형을 던졌을 경우
    {
        cout << "Catch 1 :: " << n << endl;
    }

    catch(float n)    // throw가 float형을 던졌을 경우
    {
        cout << "Catch 2 :: " << n << endl;
    }

    cout << "End Program" << endl;
    
    return 0;
}

throw가 int 값을 던질 경우


throw가 float 값을 던질 경우


try와 catch는 항상 짝을 이뤄서 사용하게 된다.
  • catch 블럭은 오직 한 가지 타입의 값만 받는다
  • try 블럭 안에서 발생하는 예외만 이어지는 catch 블럭에 잡힌다.
  • throw에 의해서 던져지면 그 함수는 즉시 종료 된다.
  • throw에 의해서 던져진 예외는 함수를 뛰어 넘어서까지 전달된다.

예외 객체의 사용

기본 값 대신에 객체를 던지는 것도 가능. 어떤 장점이 있는가?
  • 다양한 정보 전달 가능
  • 다향성을 이용한 일관된 관리가 가능

더보기

  • 예외로 던질 객체에 대한 클래스를 정의한다.
  • 예외에 관한 정보를 담을 멤버 변수들을 정의 한다.
  • 예외 객체의 생성을 편리하게 하기 위한 생성자를 만든다.

더보기

DynamicArray.cpp

더보기

저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

예외 처리, 예외에 안전한 코드 만들기  (0) 2012.12.24
예외 처리, 함수 깊숙히  (0) 2012.12.24
예외 처리  (0) 2012.12.21
오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20
다중상속(Multiplex inheritance)  (0) 2012.12.20

순수 가상 함수

순수 가상 함수는 함수의 정의 부분을 필요로 하지 않는다.

virtual void Draw() const = 0;
위와 같은 함수 원형만 가지고 있는 함수가 바로 순수 가상 함수.
  • 함수정의가 없음
  • 하나 이상의 순수 가상 함수를 가짐
  • 자식 클래스에서 오버라이딩 한다
  • 다형성을 이용하여 함수 호출
  • 추상 클래스의 객체를 만드는것은 불가능
#include <iostream>

using namespace std;

// 일반적인 '도형'을 상징하는 클래스
class Shape
{
public:
    void Move(double x, double y);
    virtual void Draw() const = 0; // 순수 가상함수 생성

    Shape();
    Shape(double x, double y);

protected:
    double _x;
    double _y;
};

Shape::Shape()
{
    _x = _y = 0.0;
}

Shape::Shape(double x, double y)
{
    _x = x;
    _y = y;
}

void Shape::Move(double x, double y)
{
    _x = x;
    _y = y;
}

// void Shape::Draw() const
// {
//     cout << "[Shape] Position = (" << _x << ", " << _y << ")" << endl;
// }

Shape::Draw() 는 함수 선언부에서 '순수 가상 함수' 로 선언되었기 때문에 정의부에서 위와 같이 주석처리하였다. 이제부터는 객체타입에 따라 자동으로 Rectangle::Draw() 또는 Circle::Draw() 가 호출된다.
저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

예외 처리, 함수 깊숙히  (0) 2012.12.24
예외 처리  (0) 2012.12.21
오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20
다중상속(Multiplex inheritance)  (0) 2012.12.20
접근제어 키워드  (0) 2012.12.20
부품간의 조립, 객체간의 연결이 다형성을 통해서 어떻게 보다 효율적으로 이루어질 수 있는지 알아보자.


원은 도형이며, 원과 도형은 'is-a' 관계를 가지고 있다는 뜻이다.



전체소스보기



위 소스는 아래와 같은 결과를 출력한다.


멤버 변수의 경우에는 부모 클래스로부터 상속 받은 것을 그대로 사용할 수 밖에 없지만 멤버 함수의 경우에는 부모 클래스에서 구현한 것이 맘에 들지 않는다면 자기에게 맞도록 새롭게 정의할 수 있다.


보관한 객체를 사용하기 그리고 문제점

실제로 수십가지의 도형이 존재할 수 있기 때문에 수십 가지의 도형별로 배열을 만들고 관리하는 것은 너무 귀찮고 비효율적이다.

대신 이 모든 클래스들이 Shape 클래스의 자식 클래스라는 점을 사용해서 하나의 배열에 모든 객체를 보관할 수 있다.

int main()
{
    // 도형들을 담을 배열을 준비
    Shape* shapes[5] = {NULL};

    // 각 타입의 객체를 생성해서 배열에 보관한다
    Shapes[0] = new Circle(100, 100, 50);
    Shapes[1] = new Rectangle(300, 300, 100, 100);
    Shapes[2] = new Rectangle(200, 100, 50, 150);
    Shapes[3] = new Circle(100, 300, 150);
    Shapes[4] = new Rectangle(200, 200, 200, 200);

    // 배열에 보관된 모든 객체를 그린다
    for(int i = 0; i < 5; ++i)
    {
        shapes[i] -> Draw();
    }

    // 배열에 보관된 모든 객체를 소멸
    for(i = 0; i < 5; ++i)
    {
        delete shapes[i];
        shapes[i] = NULL;
    }

    return 0;
}


위의 소스는 아래와 같은 모양으로 메모리를 구성한다.

동적으로 생성한 객체가 5개 있고 배열에 그 객체들이 보관되었다. 여러가지 타입의 객체를 하나의 배열에 넣어 두었기 때문에 손쉽게 사용할 수 있었다.


하지만 아래의 결과 출력처럼 Shape::Draw() 함수를 출력했다는 뜻이다.


가상함수를 통해서 이 문제를 해결할 수 있다.

#include 

using namespace std;

// 일반적인 '도형'을 상징하는 클래스
class Shape
{
public:
    void Move(double x, double y);
    virtual void Draw() const;

    Shape();
    Shape(double x, double y);

protected:
    double _x;
    double _y;
};
Draw() 함수앞에 virtual을 적어줌으로서 객체 타입에 맞는 Darw()함수가 호출된다.
  • virtual 키워드는 클래스의 정의 안쪽에서만 한 번 붙여주면 된다.
  • 클래스 밖에서 함수를 정의할 때는 virtual 키워드가 필요없다.



저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

예외 처리  (0) 2012.12.21
오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20
다중상속(Multiplex inheritance)  (0) 2012.12.20
접근제어 키워드  (0) 2012.12.20
포인터간의 형 변환, 레퍼런스간의 형변환  (0) 2012.12.20
다중상속은 두 개 이상의 부모 클래스를 동시에 상속하는 경우를 가리킨다.
학생 정보를 관리하는 프로그램을 작성시, 학부생이면서 동시에 기숙사생인 학생의 정보를 동시에 관리할 클래스를 만들때 사용할 수 있다.

#include <string>

using namespace std;

// 학부생 클래스
class UnderGradStudent
{
public:
    string name;                // 이름
    string department;          // 학부
};

// 기숙사생 클래스
class DormStudent
{
public:
    string building;            // 기숙사명
    int roomNumber;
};

// 기숙사생이면서 학부생인 경우
class UnderGrad_DormStudent:
    public UnderGrad_DormStudent,
    public DormStudent
{
public:
    
};

int main()
{
    // 기숙사생이면서 학부생인 사람의 정보 생성
    UnderGrad_DormStudent std;

    std.name = "Hyun C. Lee";
    std.department = "Info & Computer Engineering";
    std.building = "NamJeKwan";
    std.roomNumber = 1529;
    
    return 0;
}

저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20
다중상속(Multiplex inheritance)  (0) 2012.12.20
접근제어 키워드  (0) 2012.12.20
포인터간의 형 변환, 레퍼런스간의 형변환  (0) 2012.12.20
레퍼런스  (0) 2012.12.20

접근 제어 키워드 정리

접근제어 키워드에 대해서 다시 정리하면 아래와 같다
  • public
    • 모든 곳으로부터의 접근을 허용한다.
  • protected
    • 자식 클래스의 멤버 함수로부터의 접근만 허용한다.
  • private
    • 자신의 멤버 함수 외에는 접근할 수 없다.

자신의 멤버 함수에서 접근 자식 클래스의 멤버 함수에서 접근 외부에서 접근
private 멤버 O X X
protected 멤버 O O X
public 멤버 O O O

보통 일반적인 사용 가이드 라인은 아래와 같다.
  • 외부로부터 숨겨야 하는 멤버는 protected로 지정
  • 그 밖의 경우에는 public으로 지정
  • 반드시 자식 클래스에 숨기고 싶다면 private으로 지정.

저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20
다중상속(Multiplex inheritance)  (0) 2012.12.20
접근제어 키워드  (0) 2012.12.20
포인터간의 형 변환, 레퍼런스간의 형변환  (0) 2012.12.20
레퍼런스  (0) 2012.12.20
자식 클래스의 포인터를 사용해서 부모 객체를 가리키는 경우.
//부모 객체성성
DocWriter dw;

// 자식 클래스의 포인터로 부모 객체를 가리킨다.
HTMLWriter* phw = &dw    // 실패
  • 이 경우에는 컴파일러 자체 판단인 암시적 형변환을 허용하지 않음
  • (HTMLWriter*) 같은 명시적 형변환은 허용하지만 이후 문제는 작성자 몫.
  • 위 경우에는 문제 발생

  • 포인터가 아니라 레퍼런스를 사용한 경우에도 상황은 똑같다.
  • 객체 기준으로 호출 우선시할 수 있게 하는것은 아래와 같이 부른다.
    • 자바 :: Abstract Method
    • C++  :: Virtual Function
저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20
다중상속(Multiplex inheritance)  (0) 2012.12.20
접근제어 키워드  (0) 2012.12.20
포인터간의 형 변환, 레퍼런스간의 형변환  (0) 2012.12.20
레퍼런스  (0) 2012.12.20
지금까지 &는 변수의 주소를 얻을 때 사용했지만, &를 사용해서 변수를 정의해주면 레퍼런스 변수를 만들 수도 있다.
레퍼런스 변수를 정의할 때는 별명을 붙여주고 싶은 변수의 타입도 함께 적어주어야 한다.
int& ref = target;
레퍼런스의 경우에는 자체적인 메모리 공간은 존재하지 않고 오로지 이름만 존재한다.
#include <iostream>

using namespace std;

int main()
{
    /* 실험에 사용할 변수를 정의 */
    int target = 20;

    /* 레퍼런스 변수를 정의 */
    int& ref = target;

    /* 정보를 출력 */
    cout << "ref        = " << ref << endl;
    cout << "target     = " << target << endl;
    cout << "&ref       = " << &ref << endl;
    cout << "&target    = " << &target << endl;

    /* ref의 값을 변경 */
    ref = 100;

    /* 출력 */
    cout << "ref        = " << ref << endl;
    cout << "target     = " << target << endl;
    
    return 0;
}
  • ref는 target의 별명으로 설정
  • 둘 모두 같은 주소 즉, 같은 메모리 공간을 사용하고 있다는 것.
  • 반드시 생성할때 초기화가 필요
  • 처음 정의할때 외에는 다른 변수를 참조하게 만들 수 없다
  • 어떠한 타입의 변수라도 참조 가능
  • const 속성을 가지는 레퍼런스 변수는 자신의 값을 변경할 수 없게 된다.
    • 이러한 특징은 함수와 함께 사용할 때 유용하다.
  • 상수를 참조해야 할 때는 const속성을 가진 레퍼런스만이 가능
  • char c = 'A';
    const int& rci = c;
    int& ri = c;
    
저작자 표시
신고

'컴퓨터 언어 > C++' 카테고리의 다른 글

오버라이딩  (0) 2012.12.20
가상함수를 이용한 다형성 구현  (0) 2012.12.20
다중상속(Multiplex inheritance)  (0) 2012.12.20
접근제어 키워드  (0) 2012.12.20
포인터간의 형 변환, 레퍼런스간의 형변환  (0) 2012.12.20
레퍼런스  (0) 2012.12.20


티스토리 툴바