[C++] 접근 제한자, 추상 클래스(순수 가상 메서드), 오버로드(Overloading)와 오버라이딩(Overriding)


Study/C , C++ , MFC  2020. 3. 20. 00:49

안녕하세요. 명월입니다.


이 글은 C++에서 사용되는 접근 제한자, 추상 클래스(순수 가상 메서드), 오버로드(Overloading)와 오버라이딩(Overriding)에 대한 글입니다.


이전에 제가 C++의 클래스와 상속에 대해 설명한 적이 있습니다.

링크 - [C++] 클리스 선언과 사용법

링크 - [C++] 클래스 상속


접근 제한자와 오버로드, 오버라이드는 클래스와 클래스 상속과 관계가 많이 있는 키워드입니다.

접근 제한자

접근 제한자는 C++뿐 아니라 Java와 C#에서 있는 객체 접근의 제한을 두고 있는 키워드입니다.

참조 - [Java강좌 - 12] 접근제한자와 static

참조 - [C# 강좌 - 11] Static 과 접근 제한자


C++에서 사용되는 접근 제한자는 총 3가지로 다음과 같습니다.

접근자 설명
public 클래스 내부와 선언된 인스턴스 외부의 모든 접근이 가능합니다.
private 클래스 내부에서만 접근이 가능하다
protected 상속 받은 클래스와 클래스 내부에서만 접근이 가능하다.

C++은 클래스를 생성할 때 함수 선언을 하는 해더와 구현 소스를 구분하여 두가지 파일이 존재합니다. 접근 제한자는 함수 선언하는 헤더에서 사용됩니다.

#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// 클래스 선언 (이 클래스는 순수 가상 메서드를 포함하기 때문에 추상 클래스이다.)
class ATest
{
// 접근 제한자 (클래스 내부에서만 접근 가능)
private:
  // 맴버 변수
  int data1 = 0;
// 접근 제한자 (상속하는 BTest 클래스와 클래스 내부에서만 접근 가능)
protected:
  // 순수 가상 메서드
  virtual const char* function() = 0;
// 접근 제한자 (어디서든 접근 가능)
public:
  void setData1(int data1);
  int getData1();
  void print();
};
// 해더 선언
#include "ATest.h"
// 함수 구현
void ATest::setData1(int data1)
{
  // 맴버 변수에 값을 넣는다.
  this->data1 = data1;
}
// 함수 구현
int ATest::getData1()
{
  // 맴버 변수의 값을 리턴한다.
  return this->data1;
}
// 함수 구현
void ATest::print() 
{
  // 콘솔 출력
  cout << " data1 : " << this->data1 << endl;
  // 순수 가상 메서드를 호출한다. 그러나 ATest에서는 구현이 되어 있지 않기 때문에 ATest를 독자적으로 인스턴스를 할 수 없다.
  cout << " function : " << function() << endl;
}
#pragma once
// 해더 선언
#include "ATest.h"
// 클래스 선언. ATest 클래스를 상속 받는다.
class BTest : public ATest
{
// 순수 가상 메서드를 상속 받아 재정의한다.
// 즉 ATest의 print()함수에서 BTest의 function()함수를 호출하는 것.
protected:
  const char* function();
};
// 해더 선언
#include "BTest.h"
// 함수 구현
const char* BTest::function()
{
  return " function hello world ";
}
// 해더 선언
#include "ATest.h"
#include "BTest.h"
// 실행 함수
int main()
{
  // BTest를 할당
  BTest obj;
  // ATest의 data 맴버 변수에 100을 넣는다.
  obj.setData1(100);
  // data 맴버 변수의 값과 BTest의 function 함수의 값이 콘솔 출력된다.
  obj.print();

  return 0;
}

이 접근 제한자는 클래스의 특성 캡슐화 개념과 관계가 있습니다.

클래스의 캡슐화는 클래스의 기능과 목적을 분명히 하기 위해 각 맴버 변수의 접근을 제한하고 기능별 함수만 외부에서 접근 가능하게 하여 클래스의 특성을 분명하게 하는데 있습니다.

예를 들면, 우리가 가산 클래스를 만든다고 하면 가산을 담당하는 맴버 변수는 외부에서 접근을 막아 클래스의 기능을 훼손하지 않게 하며 push, get을 외부 접근을 허용하게 하여 클래스의 인스턴스의 객체의 목적을 정확하게 하는 데 있습니다.

#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// 가산을 누적시켜 값을 가지고 있는 클래스
class Sum
{
// 캡슐화에서 맴버 변수는 무조건 private
private:
  // 값을 누적하는 변수
  int data = 0;
  // 카운팅 변수
  int count = 0;
// 캡슐화에서 외부에 공개 되지 않는 함수는 protected. 
// 그러나 상속자에게도 보여서는 안되는 함수라면 private로 설정
// 보통 내부 계산 함수는 protected로 둔다
protected:
  // 가산이 호출되면 카운딩을 1 증가하고 값을 누적한다.
  void sum(int data)
  {
    this->count++;
    this->data += data;
  }
// 캡슐화에서 외부에서 값을 받거나 내보내거나 클래스를 조종하기 위한 함수는 public
public:
  // 값을 넣는 함수
  void push(int data)
  {
    this->sum(data);
  }
  // 누적 값을 반환 받는 함수
  int get()
  {
    return this->data;
  }
  // 누적 값을 콘솔에 표시하는 함수
  void print()
  {
    // 콘솔 출력
    cout << "Results in '" << this->count << "' additions - " << this->data << endl;
  }
};
#include "Sum.h"
// 실행 함수
int main()
{
  // 클래스를 할당
  Sum sum;
  // 1부터 10까지 값을 누적한다.
  for (int i = 1; i <= 10; i++)
  {
    sum.push(i);
  }
  // 콘솔 출력
  sum.print();

  return 0;
}

추상 클래스(순수 가상 메서드)

추상 클래스란 클래스이긴 한데 완벽한 클래스가 아닌 함수 정의만 하고 구현하지 않는 함수를 가지고 있는 클래스를 뜻합니다. 추상 클래스는 독자적으로 할당이 불가능하고 클래스를 상속시켜야 사용할 수 있는 클래스입니다.

Java나 C#에서는 추상 클래스라고 하면 class 앞에 abstract 키워드를 넣어 추상클래스 임을 명시하지만 c++은 추상 클래스를 명시하는 키워드는 없습니다.

단지 virtual 키워드를 가지고 구현하지 않고 「=0」을 입력한 순수 가상 메서드를 가지면 추상 클래스로 됩니다.

위 예제에서 ATest.h에서 「virtual const char* function() = 0;」로 함수 선언을 했지만, 구현 소스인 ATest.cpp를 보면 함수를 구현하지 않았습니다.

이 상태에서 ATest를 할당하게 되면 에러가 발생합니다.

위 예제에서는 function 함수를 BTest에서 상속 받아 재정의 한다음 ATest의 print() 함수를 호출하면 BTest의 function함수가 호출되는 것을 확인할 수 있습니다.


오버로드(Overloading)와 오버라이딩(Overriding)

오버로드(Overloading)와 오버라이딩(Overriding)는 이름이 비슷한 관계로 항상 두 용어의 차이와 비교를 합니다만 실제의 기능은 서로 전혀 관계없는 키워드입니다.

오버로드(Overloading)는 같은 함수명으로 파라미터를 다르게 하여 이름은 같지만 처리가 다르게 구현된 것을 의미합니다.

오버라이딩(Overriding)는 클래스를 상속 받을 때, 특정 함수를 재정의 하는 것을 의미합니다.

#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// 클래스 선언
class ATest
{
// 전체 공개 접근 제한자
public:
  void print();
};
// 해더 선언
#include "ATest.h"
// 함수 구현
void ATest::print()
{
  // 콘솔 출력
  cout << " ATest - print() " << endl;
}
#pragma once
// 해더 선언
#include "ATest.h"
// 클래스 선언, ATest 클래스를 상속 받았다.
class BTest : public ATest
{
public:
  // 오버라이딩(Overriding)이다. ATest의 print()함수를 재정의하였다.
  virtual void print();
  // 오버로드(Overloading)이다. print()함수가 있는데도 파라미터를 달리하고 함수명을 같게하여 정의하였다.
  void print(const char* param);
};
// 해더 선언
#include "BTest.h"
// 함수 구현
void BTest::print()
{
  // 콘솔 출력
  cout << " BTest - print() " << endl;
  // 부모 클래스의 print()함수를 호출
  ATest::print();
}
// 함수 구현
void BTest::print(const char* param)
{
  // 콘솔 출력
  cout << " BTest - print(" << param << ") " << endl;
}
// 해더 선언
#include "ATest.h"
#include "BTest.h"
// 실행 함수
int main()
{
  // 클래스 할당
  BTest obj;
  // print()함수를 호출하면 BTest의 print()함수가 호출된다.
  obj.print();
  // print(const char*)가 호출된다.
  obj.print("hello world");

  return 0;
}

위 예제에서 BTeset를 보시면 print 함수가 두개 있습니다.

파라미터가 없는 print는 오버라이딩(Overriding)에 대한 예제이고, print(const char* param)는 오버로드(Overloading)에 대한 예제입니다.


여기까지 C++에서 사용되는 접근 제한자, 추상 클래스(순수 가상 메서드), 오버로드(Overloading)와 오버라이딩(Overriding)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.