반응형

 

programmers.co.kr/learn/courses/30/lessons/42746

 

코딩테스트 연습 - 가장 큰 수

0 또는 양의 정수가 주어졌을 때, 정수를 이어 붙여 만들 수 있는 가장 큰 수를 알아내 주세요. 예를 들어, 주어진 정수가 [6, 10, 2]라면 [6102, 6210, 1062, 1026, 2610, 2106]를 만들 수 있고, 이중 가장 큰 ��

programmers.co.kr

위 문제를 c++로 풀면서 string을 썼는데

문득 Java 를 배울때 string 을 쓰기 보다는 stringbuilder 를 쓰는게 퍼포먼스 면에서 좋다고 배웠던 기억이 났다

그러나 해당 코드를 c++로 짜는 중이었기 때문에 c++ 에 stringbuilder는 없어서 쓸 수 없었고 대신 stringstream이 비슷한 역할을 하는 것 같아서, stringstream으로 코드를 작성한뒤 성능 측정을 비교해 봤는데

//A코드 : stringstream 사용
#include <string>
#include <vector>
#include <algorithm>
#include <sstream>

using namespace std;

bool cmp(int a, int b)
{
    stringstream ss1;
    stringstream ss2;
    ss1 << a << b;
    ss2 << b << a;
    return ss1.str() > ss2.str();
}


string solution(vector<int> numbers) {

    stringstream ss;
    sort(numbers.begin(), numbers.end(), cmp);
    for (auto& i : numbers)
    {
        ss << i;
    }
    return ss.str()[0] == '0' ? "0" : ss.str();
}
//B코드 : string만 사용
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

bool cmp(int a, int b)
{
    string s1 = to_string(a) + to_string(b);
    string s2 = to_string(b) + to_string(a);
    return s1 > s2;
}

string solution(vector<int> numbers) {

    string answer = "";
    sort(numbers.begin(), numbers.end(), cmp);
    for (auto& i : numbers)
    {
        answer += to_string(i);
    }
    return answer[0] == '0' ? "0" : answer;
}

 

B코드, 즉 string을 사용한 쪽이 퍼포먼스가 A에 비해서 더 잘나오는것을 볼 수 있었다, 어떤 부분들은 2~3배 가까이 차이가 난다. 

c++ 의 stringstream 은 Java 의 stringbuilder와 달리 퍼포먼스를 위해 쓰는 용도는 아닌것 같다,

특히나 다른 c++ 사용자들도 stringstream을 사용했을때 느리더라 라는 글들도 몇몇개 보인다.

 

참고 :

cppjava.tistory.com/63

 

[퍼온글] 문자열 숫자로 바꾸기 stringstream

출처 : http://www.buggymind.com/83 요즘 C++로 Admission Control System을 개발중입니다. 근데 프로그램을 짜다 보면 으례 그렇겠지만 스트링을 숫자로, 숫자는 스트링으로 변환할 일이 많이 생기더군요. 종전

cppjava.tistory.com

stackoverrun.com/ko/q/1443219

 

반응형
반응형

코드

template <typename T>
class SingleTonBase
{
private:
	static T* instance;

public:
	static T* GetInstance()
	{
		if (!instance)
		{
			instance = new T;
		}
		return instance;
	}
    
    	static void DeleteInstance()
	{
		if (instance)
		{
			delete instance;
            		instance = nullptr;
		}
	}

protected:
	SingleTonBase(){}
	virtual ~SingleTonBase(){}

public:
	SingleTonBase(const SingleTonBase&) = delete;
	SingleTonBase& operator = (const SingleTonBase&) = delete;
};

template<typename T>
T* SingleTonBase<T>::instance = nullptr;



//클래스에서 상속을 받고 friend 키워드를 통해 적용
class MyClass : public SingleTonBase<MyClass>
{
	friend class SingleTonBase<MyClass>;
private:
	MyClass() {};
	~MyClass() {};

public:
	void print(HDC a_hDC, int a_x, int a_y)
	{
		std::wstringstream wss;
		wss << L"MyClass : " << (int)GetInstance();
		TextOut(a_hDC, a_x, a_y, wss.str().c_str(), wss.str().length());
	}
};

 

테스트

같은 주소를 가리키고 있다, 즉 언제나 GetInstance() 통해서만 instance를 받을수 있고 그 주소는 오직 하나의 주소만 가리키는걸 볼 수 있다.

반응형
반응형

C언어, Queue, pop() 구현하던 도중 shallow copy와

비슷한 문제 발생

 

내가 구현해본 pop은 첫번째 노드의 데이터를 리턴함과 동시에 Queue 에서 제거하는 함수로

1. char * Data 에 첫번째 노드 데이터를 복사하고 그걸 나중에 리턴한다 

2. Queue의 Head가 첫번째 노드의 다음 주소를 임시 노드에 담아둔다

3. 첫번째 노드를 free 해준다

4. Queue 사이즈 1 감소시킨다

5. 첫번째 노드에 임시노드에 적혀있던 주소를 담는다, 이로써 다음 노드가 첫번째 노드가 된다

6. 데이터를 리턴한다

 

그러나 1번 char * Data 에 첫번째 노드 데이터를 담고 그걸 나중에 리턴할때 쓰레기값 또는 이상한 값이 출력 됐다.

 

 

원인

데이터를 복사한게 아닌, 데이터(char 배열)의 주소를 복사하게 됐다

C++ 에서는 동일한 클래스 타입의 두개의 인스턴스가 존재할때 복사 생성자를 통해서, 인스턴스가 가지고 있는 데이터들을 다른 인스턴스에 복사할 수 있다, 문제는 이때 컴파일러가 만들어주는 기본 복사 생성자를 쓰게되면, 순수하게 데이터 변수들의 값만 복사하게 되고, 주소를 가지고 있는 타입도, 주소를 복사 하게 된다, 그리고 두개의 데이터가 동일한 주소를 가리키는 사태가 벌어진다. 이렇게 되면 원본이던 사본이던 간에 어느 한쪽이 소멸되어 주소가 사라지면, 복사 받은 쪽에서도 주소가 사라져서 결국 이상한 주소를 가리킨다.

그리고 실제로 196번 라인에서 _pQueue->pHead 의 메모리를 해제할때

_pQueue->pHead->data 조차 사라지며, 그걸 담아 뒀던 char* Data 도 사라진 주소를 가리켜 결국

이상한 주소를 가리키게 됐다

 

 

 

해결

pop의 인자로 배열을 input 으로 넣고 해당 배열에 data를 복사하도록, 그리고 주소를 대입하는게 아닌, strcpy_s를 사용해 실제 문자열을 복사하도록 했다

이후 pop한 데이터가 쓰레기 값을 출력하는 사태는 없어졌다.

 

반응형
반응형

member initializer list 사용시 주의

또다른 HEAP CORRUPTION DETECTED 문제 발생

컨테이너 클래스 vector 만들었는데

 

생성자 코드, member initializer list 를 통해 초기화

resize 함수에서 , 동적 할당된 배열이므로, delete[] m_pArray 했는데 HEAP CORRUPTION DETECTED 발생

 

원인

member initializer list 통한 초기화를 할때

m_iSze, m_iCapacity, m_pArray 순으로 초기화 되길 바랬지만, 컴파일러는 멤버 변수 선언 순서대로 초기화 한다, 그래서 m_pArray 부터 초기화를 했고 m_iCapacity 0값인 상태에서 m_iCapacity + 2 했으니 실상

2만큼의 크기를 가지는 배열을 초기화 한것

 

 

선언 순서를 바꾼뒤 다시 빌드했다

이후  이상 HEAP_CORRUPTION_DETECTED 런타임 에러가 발생하지 않았다.

 

 

결론

member initializer list 를 통한 멤버 변수 초기화를 할때는, 

 

생성자()

 : 순서1, 순서2, 순서3  // 이런순으로 초기화 되는게 아니라

{}

 

 

멤버 변수 선언 순서로 초기화를 하기 때문에 주의를 하자

반응형
반응형

template

  • 템플릿 매개 변수에 대해 제공하는 인수를 기반으로 컴파일 시간에 일반 형식 또는 함수를 생성하는 구문

  • 함수 또는 클래스에 사용되는 자료형이, 특정 자료형에 의존하지 않게 할때 쓰임 ( Generic Programming )

  • 여러가지 자료형에 대해서 비슷한 코드를 반복해서 작성하는 것을 방지해줌

 

 

함수 템플릿

"template <typename T>" 또는 "template<class T>" 함수위에 한줄 작성, 여기서 T 자료형으로 매크로(#define)처럼 치환될 타입이름, 아직 정해지지 않은 타입이름이다, 많이 쓰이는 이름으로 T 쓰인다, T 쓰지 않고, T1, T2 등으로 써도 컴파일된다. 

T 매개변수의 타입으로도 쓰일수 있고, 함수 스코프 안에서 자유롭게 T 사용할 있다

T 함수 호출할때 , 함수이름<자료형>(); 이런식으로 미리 정해서 호출할 수도 있고

정하지 않고도 81번라인~ 85번라인의 코드처럼 함수를 호출 해도 템플릿 코드가 동작한다.

74 라인의 경우, OutputType<int>(); T int 치환된것이다

int, float 말고도 사용자 정의 자료형(class) 들어갈 있다.

 

주의

사용자 정의 자료형을 함수 템플릿의 T 쓸때 주의할 점은 함수 안에서 비교, 산술 등등의 연산자를 사용하는 경우 사용자 정의한 클래스에 연산자 오버로딩이 되있어야 한다.

그렇지 않을 경우 error C2676 발생 한다

error C2676 이란? https://docs.microsoft.com/ko-kr/cpp/error-messages/compiler-errors-2/compiler-error-c2676?view=vs-2019

 

IsSame 실행 있게 MyClass == 연산자 오버로딩을 해주고, int 멤버 변수를 만든뒤, 생성자로 기본값을 설정해야 선언 있게 만들었다

정상 작동하는걸 있다, template 함수에 내가 만든 클래스형을 인자로 호출 할때는 함수 안에서 연산자를 쓰는지 여부를 파악해야 하며, 연산자를 쓴다면 연산자 오버로딩이 필요하다.

 

 

 

또한 위처럼 typename 여러 있다.

 

 

클래스 템플릿

클래스를 디자인할때 템플릿을 사용할 있다,

클래스 선언시에 클래스옆에<자료형> 붙여서

어떤 자료형이 오게 될지를 먼저 정해줘야 한다.(Template은 컴파일 시간에 동작하기 때문에)

 

또한 템플릿 클래스의 메소드(멤버 함수)들은 T 자유롭게 사용할 있다.

tem2 T float 정해졌기 때문에 tem2 메소드 Output2 인자로 float 요구하는 있다.

 

 

주의

template 클래스에서는 위와 같이 헤더파일과 .cpp 파일 분리를 해도, 원활한 분리가 되지 않는다.

빌드를 하면 아래와 같은 링크 에러가 발생한다.

error LNK2019 가   발생했다 .

 

 

원인

헤더 파일을 .cpp 파일에 include 하게 되면 헤더파일의 내용을 cpp 파일의 맨앞부터 복사 해서 붙여놓은 것과 같다, 컴파일은 .cpp파일에서 하게 되어있다. 필요한 함수 또는 변수 선언이 있다면 헤더파일에서 찾는것이다.

그래서 Template_2_main.cpp 파일의 경우 6번라인에 있는 클래스 CTemplate "CTemplate.h" 파일에서 찾은뒤 T int 치환해서 instance 생성한다

문제는 Output 함수의 body 컴파일 할때는 CTemplate.cpp에서 컴파일을 하는데

CTemplate.h include CTemplate.cpp 파일의 경우 main 함수에서 int T 정했다는 수가 없다. CTemplate.cpp T 쓰는함수들은 여전히 동일하게 T인것이다.

그래서 Output 함수를 컴파일 할때 T int 치환을 안하게 된다

 

해결책

explicit instantiation

CTemplate.cpp 파일에 위와 같이 explicit instantiation(명시적 인스턴스화) 해주면 된다.

 

explicit instantiation 하는법

 

template 클래스이름<특정자료형>::함수이름(특정자료형 매개변수이름) ;

 

반응형

'C++' 카테고리의 다른 글

string vs stringstream  (0) 2020.09.04
template singleton  (0) 2020.07.21
c++ typeid 연산자  (0) 2020.03.16
References(L-value reference, R-value reference)  (0) 2020.03.13
C++ lvalue, rvalue, xvalue, glvalue, prvalue  (0) 2020.03.12
반응형

c++ typeid 연산자

출처 https://docs.microsoft.com/ko-kr/cpp/cpp/typeid-operator?view=vs-2019

typeid(type-id)

typeid(expression)

 

  • typeid 연산자를 사용 하면 런타임에 개체의 형식을 확인할 있음
  • typeid 결과값은 const type_info& 이다, 

출처 https://docs.microsoft.com/ko-kr/cpp/cpp/type-info-class?view=vs-2019

class type_info {

public:

    type_info(const type_info& rhs) = delete; // cannot be copied

    virtual ~type_info();

   

//--연산자 오버로딩

    _CRTIMP_PURE bool operator==(const type_info& rhs) const;

    type_info&            operator=(const type_info& rhs) = delete; // cannot be copied

    _CRTIMP_PURE bool operator!=(const type_info& rhs) const;

//연산자 오버로딩--

 

 

_CRTIMP_PURE int before(const type_info& rhs) const;

 

    size_t hash_code() const;

    size_t hash_code() const noexcept;

    _CRTIMP_PURE const char* name() const;

    _CRTIMP_PURE const char* raw_name() const;

};

 

 

 

typeid(자료형 또는 변수이름 또는 함수이름 또는 인스턴스 등등).name() 실행하면

자료형의 이름을 리턴한다

 

 

또한 리턴하는 자료형의 이름은 const char* 라는 것을 있다

혹시나 해서 const의 위치까지 구분해서 리턴하는지 테스트 해봤는데

const 의 위치까지 따로 구분해서 리턴하지는 않는다

참고로 포인터 변수는 const의 위치에 따라서 아래와 같이 수정 가능한 부분이 다르다

 const 자료형*  : 역참조 값을 수정 X , 가리키는 주소는 수정 O

 자료형* const  : 역참조 값을 수정 O, 가리키는 주소 수정 X

 const 자료형* const : 역참조값 수정 X, 가리키는 주소 수정 X

 

그러나 typeid(자료형).name() 으로 위 사항들까지는 구별 할 수 없다.

그저 자료형 앞에 const 가 있으면 출력할때

"자료형 const" 으로 출력하고

포인터 변수인 경우 "자료형 * const" 로 출력한다

 

 

거의   모든   자료형의   이름을  const char * 로  리턴받을 수   있다 .

 

반응형
반응형

Reference Variable

(c++11, L-value reference)

L-value, 기존 메모리 공간(변수) 대한 별명을 만들어 변수처럼 쓴다,

별명만 붙이는 것이기 때문에, 메모리 주소는 동일한 공간을 쓴다

Reference(참조)라는 뜻에서 있듯이, 어떤 변수를 참조하게 만들어 준다

typedef 다르다(typedef 자료형에 별명을 주는것)

 

선언법

 

자료형& 레퍼런스식별자 = 변수;

 

 

pass by reference 할때 포인터 대신에 사용할 있다,

하지만 포인터와는 다르다, 구지 포인터를 Reference Variable 비슷하게 쓴다면

 

자료형 * const ptr = &변수;

비슷한 모양이다, 선언시에 정해진, 가리키고 있는 변수의 주소를 수정 없다

그러나 포인터는 주소가 가리키는 값에 접근하기 위해 indirection operator(역참조)* 사용하지만

Reference Variable 역참조를 없다,

 

int main()

{

    int a = 8;

    int &a_ref = a; // 포인터와 다르게 &a 대입하지 않음

 

    std::cout << a_ref << std::endl; // 결과는 8 나옴

    return 0;

}

 

R-value Reference

(c++11)

c++11 부터 move semantic 개념이 도입되면서, 기존 refernce variable

L-value refernce, R-value reference 나뉘게 되었다

 

선언법
 

자료형&& 레퍼런스식별자 = R-value;

 

 

비교 연산자(&&)와는 아무 관련 없음

R-value 대입할 있다, 식별자가 없는 값들만 대입 있다.

 

R-value, 즉 곧 사라지는 값, 식별자가 없는 값만 대입 할 수 있다.

 

 

R-value reference parameter

 

 

이때 20번라인 doSomething L-value 인자로 받고

21번라인 doSomething R-value 인자로 받았다

반응형
반응형

C언어, C++11이전까지의 L-value, R-value

 

L-value : locator value, 저장 장소 위치의 값, 할당 연산자 기준으로 왼쪽에 오는 값

R-value : value of an expression, 수식의 값, 할당 연산자 기준으로 오른쪽에 오는 값

 

 

 

C++11 이후

출처: https://docs.microsoft.com/ko-kr/cpp/cpp/lvalues-and-rvalues-visual-cpp?view=vs-2019

glvalue, xvalue, prvalue 개념이 새로 생겼다 일단 

 

 

L-value와 R-value 부터 정리하면

 

 

위 코드에서 left_value, right_value, result 는 모두 L-value 로서 식별자(변수이름)를 가지고 다른 데이터를 복사해서 자신의 상태(값)를 변경 할 수 있다.

3, 7, 0 은 모두 R-value 이다.

R-value는 다른 데이터의 메모리를 복사할 공간이 없거나, 식별자가 따로 존재하지 않기 때문에 대입이 불가능하다.

또한 3,7,0 은 식별자가 따로 존재 하지 않기 때문에, 따로 변수처럼 접근 할 수 없어, 일시적인 값, 사라지는 값이라고 할 수 있다

 

 

L-value는 Locator value, 존재하는 메모리를 식별할 수 있는지의 여부, 즉 식별자가 있어야 하고, 식별자를 가지고 다른 값을 복사 받을 수 있으면 L-value 라고 할 수 있다.

그러나 배열타입, 불완전한타입, const 타입은 Lvalue 로 올 수 없다

 

R-value는 value of an expression, 즉 수식을 계산한 후의 값이다, 위에서 left_value + right_value 는

수식 (left_value의 값) + (right_value의 값)의 결과 값 10 이 되고

10( left_value + right_value 의 결과 값 )은 식별자가 존재 하지 않기 때문에 R-value인 것이다 

 

 

 

이제  glvalue, xvalue, prvalue 를 파헤쳐 보면

크게 식별자의 유무에 따라서 나눌 수 있고

식별자 있음 : glvalue( xvalue, lvalue)

식별자 없음 : prvalue

 

move의 가능 여부 에 따라서 나눌 수 있다

move 가능 : rvalue(xvalue, prvalue)

move 불가능 : lvalue

 

여기서 move의 가능이란, move semantics 가 가능하다 또는 값이 메모리에서 이동 될 수 있는것을 말한다.

 

 

위 MSDN 정리 그림을 https://docs.microsoft.com/ko-kr/cpp/cpp/lvalues-and-rvalues-visual-cpp?view=vs-2019

다시 표시하면

이렇게 볼 수 있다

 

 

lvalue : identity 가짐, move 없는 표현식들

 

 

xvalue : identity 가짐, move 있는 표현식들

 

 

prvalue : identity 안가짐, move 있는 표현식들

 

 

glvalue : identity 가지고 있는 표현식들(lvalue, xvalue)

 

 

rvalue : move 있는 표현식들(prvalue,xvalue)

반응형
반응형

C++ Inheritance (상속) _ 다형성, 업캐스팅, 다운캐스팅

 

다형성(Polymorphism)

  • 생물학에서 유래한 단어, 사전적 의미로 동일한 종에 속하는 생물이라 할지라도 다양한 변이를 가지는 현상을 말함, 즉 여러개의 형태를 갖는다는 것,
  • OOP의 특징중 하나

 

C++ 에서 다형성을 가능하게 해주는 요소들

  1. 상속 관계에서 상위 클래스 또는 하위 클래스로 형변환(up casting, down casting)
  2. 메소드 오버라이딩, 가상함수
  3. 순수 가상 함수
  4. 함수 오버로딩
  5. 연산자 오버로딩

 

상속 관계에서 상위 클래스 또는 하위 클래스로 형변환

  • Up casting : 상속관계에서  하위 클래스가 상위클래스로 형변환 하는 것
  • Down casting : 상속관계에서 업캐스팅했던 상위 클래스 인스턴스가 하위클래스로 형변환 하는 것 

 

Up casting 방법

상위클래스* 클래스이름 = new 하위클래스; 

 

이때 하위클래스 와 상위클래스의 접근 권한이 아래와 같이

 

class 하위클래스 : public 상위클래스

{

}

 

public 으로 되있어야 한다.

 

 

 

 

Down casting 방법

하위클래스* 클래스이름 = (하위클래스*)업캐스팅된상위클래스변수;

 

 

 

 

ex1)

동물

  ↑

고양이

실행결과

40번라인에 있는 animal은 Animal 이 아닌 Cat을 할당 받았고 sound()를 호출해보면 ??? 가 뜬다 즉 하위 클래스를 할당 했더라도, 상위클래스 처럼 쓸 수 있는것이다, 이렇게 상위 클래스의 자료형에 하위 클래스를 할당하는 것을 업캐스팅 이라고 한다,

 

41번 라인은 Down casting 이다, 상위 클래스처럼 쓰이고 있는 하위클래스를 본래의 자료형에 맞게 할당하는것 이때 상위 클래스에서 하위클래스로 형변환 할때는 명시적 형변환이 필요하다, 그뒤 sound()를 호출해보면 Cat::Sound() 함수 정의대로 Mero~ 를 호출 하는것을 볼 수 있다.

 

 

주의점

이러한 형변환 성질을 이용해 여러 객체들을 상속관계로 디자인하고 최상위 클래스들로 Upcasting 한뒤, 객체들을 한꺼번에 하나의 배열에 두고 관리 할 수도 있다, 즉 한번에 for문 하나로 delete 할수도 있게 된다, 그러나 Upcasting 상태에서 delete 하게 되면, 하위클래스의 소멸자는 호출되지 않는다. 

 

 

업캐스팅 한뒤 delete 하게되면 상위 클래스 소멸자만 호출된다.

위 문제를 해결 하기위해서 즉, 하위클래스의 소멸자까지 호출해주게 하기 위해서는 다시 다운캐스팅을 한뒤 delete 해야 한다.

 

 

위처럼, down casting을 하여, delete 해주면 되긴하다, 그러나 만약 상속계층이 cat에서 끝나지 않고 하위클래스가 더 존재한다면?, 그리고 그중에 본래의 하위클래스 형태를 추적하기 힘들다면? 아래와 같은 상황을 보자

 

 

 

 

 

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include <iostream>
 
class Animal
{
public:
    Animal()
    {
        std::cout << "Animal 생성자" << std::endl;
    }
 
    ~Animal()
    {
        std::cout << "Animal 소멸자" << std::endl;
 
    }
};
 
class Cat :public Animal
{
 
 
public:
    Cat()
    {
        std::cout << "Cat 생성자" << std::endl;
    }
 
    ~Cat()
    {
        std::cout << "Cat 소멸자" << std::endl;
    }
};
 
 
class SuperCat :public Cat
{
 
 
public:
    SuperCat()
    {
        std::cout << "SuperCat 생성자" << std::endl;
    }
 
    ~SuperCat()
    {
        std::cout << "SuperCat 소멸자" << std::endl;
    }
};
 
class MegaCat :public SuperCat
{
 
 
public:
    MegaCat()
    {
        std::cout << "MegaCat 생성자" << std::endl;
    }
 
    ~MegaCat()
    {
        std::cout << "MegaCat 소멸자" << std::endl;
    }
};
 
 
class SpecialCat :public MegaCat
{
 
 
public:
    SpecialCat()
    {
        std::cout << "SpecialCat 생성자" << std::endl;
    }
 
    ~SpecialCat()
    {
        std::cout << "SpecialCat 소멸자" << std::endl;
    }
};
 
 
 
int main()
{
    Animal* superCat = new SuperCat;
    std::cout << std::endl;
    Animal* superCat_2 = new SuperCat;
    std::cout << std::endl;
    Animal* megaCat = new MegaCat;
    std::cout << std::endl;
    Animal* cat = new Cat;
    std::cout << std::endl;
 
    Animal* animalArr[4= { superCat,superCat_2,megaCat,cat };
 
    for (int i = 0; i < 4; i++)
    {
        delete animalArr[i];
    }
 
 
 
    return 0;
}
cs

실행 결과

예상대로 상위클래스 소멸자만 호출됐다

특히나 위와같이 배열에 담고서 관리할 경우 원래 형태가 어떤 것이었는지 추적하기 어렵다, 이때 한가지 방법이 존재하는데, 가상함수 라는 것이다, 가상함수는 따로 다시 정리하겠지만 여기서는 최상위 클래스의 소멸자에 virtual 이라는 키워드만 붙여주면 된다

 

virtual 키워드는 가상함수를 정의할 때 쓰인다

오직 최상위 클래스 소멸자 앞에 virtual 만 붙였을 뿐인데, 아래와 같은 결과가 나온다

정상적으로 소멸자가 모두 호출된것을 볼 수 있다.

 

즉 최상위 클래스의 소멸자는 가상함수로 만드는게 좋다

 

 

 

정리

  • 상위클래스와 하위클래스간에는 형변환이 가능한데, 이를 Up Casting, Down Casting 이라한다
  • Up Casting 하면 하위 클래스를 상위 클래스처럼 사용 가능하다
  • 어떤 객체들을 최상위 클래스로 Upcasting 하면 하나의 배열로 관리하기 용이하다 그러나 최상위 클래스의 소멸자는 가상함수로 만들어 소멸자가 원활하게 호출되게 해야한다.

 

반응형
반응형

C++ Inheritance(상속)

  • 어떤 클래스가 다른 클래스와 기반(base) 파생(derived) 관계를 가질때, 파생클래스 에서는 기반클래스의 필드(멤버변수)   메소드(멤버함수) 들을 사용할 있다.
  • 하지만 기반 클래스의 private 변수,함수 들은, 파생클래스에서도 접근이 불가능하다, protected, public 가능하다, 반면에 friend 키워드를 쓰면 가능하긴하다(friend 상속에 관계없이, 모든 변수, 함수에 접근할 있도록 접근 허용 권한을 주는것)

 

 

 

상속 관계 짓는 방법

클래스를 디자인할때, 콜론(:) 넣고 뒤에 접근제어자와 다른 클래스의 이름 붙이면 된다

 

ex1)

 

파생클래스 : 외부접근권한 기반클래스

class MyClassB : public MyClassA

 

 

이때 외부 접근권한을 public, private 정해 있는데

public 경우 클래스 외부에서도 기반 클래스의 public 변수, 함수들에 접근할 있게 되고

private 경우 파생클래스만 기반 클래스의 public 변수 함수들에 접근할 있다

 

 

ex2)

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
class CParent
{
public:
    CParent()
        :m_iA(0),m_iB(0),m_iC(0)
    {
        std::cout << "CParent 생성자" << std::endl;
    }
    
    ~CParent()
    {
        std::cout << "CParent 소멸자" << std::endl;
    }
public:
    int        m_iA;
 
protected:
    int        m_iB;
 
private:
    int        m_iC;
 
public:
    void Output()
    {
        std::cout << "A : " << m_iA << std::endl;
        std::cout << "B : " << m_iB << std::endl;
        std::cout << "C : " << m_iC << std::endl;
    }
};
 
class CChild : public CParent
{
public:
    CChild()
        :m_iD(0)
    {
        m_iB = 200;
        // m_iC는 private 이기 때문에 자식 내부에서도 접근이 불가능 하다
        //m_iC = 300; // error
        std::cout << "CChild 생성자" << std::endl;
    }
 
    ~CChild()
    {
        std::cout << "CChild 소멸자" << std::endl;
    }
 
protected:
    int        m_iD;
};
 
class  CChild1 : private CParent
{
public:
    CChild1()
        :m_iD(0)
    {
        m_iA = 100;
        m_iB = 200;
        std::cout << "CChild1 생성자" << std::endl;
 
    }
 
    ~CChild1()
    {
        std::cout << "CChild1 소멸자" << std::endl;
    }
 
private:
    int        m_iD;
};
 
class CChildChild : public CChild
{
public:
    CChildChild()
        :m_iE(0)
    {
        m_iD = 500;
        std::cout << "CChildChild 생성자" << std::endl;
    }
 
    ~CChildChild()
    {
        std::cout << "CChildChild 소멸자" << std::endl;
 
    }
 
private:
    int        m_iE;
};
 
 
int main(void)
{
    /*
    상속 관계에서의 생성자 호출 순서 : 부모 -> 자식 순으로 호출됨
    상속관계에서의 소멸자 호출 순서 : 자식 -> 부모 순으로 호출됨
    */
    CParent            parent;
    std::cout << std::endl;
    CChild            child;
    std::cout << std::endl;
    CChild1            child1;
    std::cout << std::endl;
    CChildChild        childchild;
    std::cout << std::endl;
 
    parent.m_iA = 100;
 
    child.m_iA = 200;
 
    // CChild1 클래스는 CParent를 private 상속을 하고 있으므로 CParent에 
    // Public으로 설정되어있는 멤버들도 외부에서는 접근이 불가능하다.
    //child1.m_iA = 300;
    //child1.Output();
 
 
    return 0;
}
cs

실행화면

 

정리

  1. 상속관계에서 파생 클래스는 기반 클래스의 생성자를 먼저 호출한뒤 그 다음에 파생 클래스의 생성자를 호출한다 
  2. 소멸자는 생성자의 역순으로 호출된다, 즉 파생클래스 소멸자 -> 기반 클래스 소멸자 순으로 호출 된다
  3. 파생클래스 : public 기반클래스 -> 외부에서도 기반 클래스의 public 변수및 함수 접근 가능,                             
  4. 파생클래스 : private 기반클래스 -> 외부에서 기반클래스의 모든 변수 및 함수 접근 불가능
  5. protected 접근 제어자는 파생클래스가 기반클래스의 변수 또는 함수에 접근할 수 있도록 해준다
반응형

+ Recent posts