본문 바로가기
프로그래밍/C++

C++ 언어의 특징

by Rozentea 2024. 1. 26.
해당 글은 공부를 하면서 적은 글이기 때문에 틀릴 수 있습니다. 참고용으로만 봐주세요~

 

1. C++ 언어의 설계 목표


C++ 언어의 기본적인 설계 목표에 대해 알아봅시다.

 

1) C 언어로 작성된 프로그램과의 호환성(Compatibility)을 유지한다.

기존에 작성된 C 언어의 문법적 체계를 그대로 계승한다. 뿐만 아니라 C 언어로 작성되어 컴파일된 목적 파일(Object File)이나 라이브러리를 C++  프로그램에서 링크하여 사용할 수 있도록 한다.

 

2) 소프트웨어의 재사용을 통해 소프트웨어 생산성을 높이고, 복잡하고 큰 규모의 소프트웨어 작성, 관리, 유지 보수를 쉽게 하기 위해서 객체지향적 개념을 도입한다.

 

3) 타입 체크를 엄격히 하여 실행 시간 오류의 가능성을 줄이고 디버깅을 돕는다.

 

4) 실행 시간의 효율성 저하를 최소화한다.

객체 지향 개념의 도입으로 멤버 함수의 호출이 잦아지고 이로 인해 발생하는 실행 시간 저하의 비효율성을 막기 위해 인라인 함수 도입 등 함수 호출로 인한 시간 저하를 막는다.

 

2. C 언어에서 추가된 기능


C++ 언어는 C 언어의 문법적 규칙을 그대로 승계하며, 프로그래밍의 편리와 다양성을 위해 다음과 같은 기능을 추가했습니다.

 

 1. 인라인 함수(Inline Function)

자주 호출되는 함수의 경우, 함수 호출 대신 함수 코드를 확장 삽입하는 방식이며, 실행 시간을 줄여준다.

 

인라인 함수함수가 호출될 때마다 발생하는 일정량의 성능 오버헤드를 제거할 수 있다는 장점이 있습니다.

함수를 호출하려면 스택에서 반환 주소를 푸시하고, 인수를 스택으로 푸시하고, 함수 본문으로 이동한 다음, 함수가 완료되면 반환 명령을 실행합니다. 이러한 프로세스를 인라인 처리하여 제거합니다. (MSND 문서 참고)

 

하지만 크거나 복잡한 연산을 하는 함수의 경우 큰 의미가 없고, 인라인 함수가 모든 함수 호출에 대해 적절한 위치에서 확장되기 때문에 인라인 함수가 길거나 인라인 함수를 여러번 호출하는 경우 컴파일된 코드를 약간 더 크게 만들 수 있습니다.

더보기

함수는 함수가 호출될 때마다 발생하는 일정량의 성능 오버헤드가 있다는 단점이 있다.

이는 CPU가 다른 레지스터와 함께 실행 중인 현재 명령어의 주소를 저장해야 하므로 (나중에 반환할 위치를 알 수 있도록) 모든 함수 매개 변수를 생성해야 한다. 할당된 값을 사용하면 프로그램이 새 위치로 분기된다. 내부에서 작성된 코드(인스턴트 코드)가 훨씬 더 빠르다.

 

크거나 복잡한 태스크를 수행하는 함수의 경우 함수 호출의 오버헤드는 함수가 실행되는 데 걸리는 시간과 비교할 때 중요하지 않다. 그러나 일반적으로 사용하는 작은 함수의 경우, 함수 호출에 필요한 시간이 실제로 함수 코드를 실행하는 데 필요한 시간보다 훨씬 많은 경우가 있다. 이로 인해 상당한 성능 저하가 발생할 수 있다.

 

C++은 인라인 함수(Inline Function)라는 내부에서 작성된 코드의 속도와 함수의 장점을 결합하는 방법을 제공한다.

inline 키워드는 컴파일에서 함수를 인라인 함수로 처리하도록 요청한다.

컴파일러가 코드를 컴파일 하면 모든 인라인 함수가 인-플레이스(in-place) 확장된다.

즉, 함수 호출이 함수자체의 내용 복사본으로 대체되어 함수 오버헤드가 제거된다.

단점은 인라인 함수가 모든 함수 호출에 대해 적절한 위치에서 확장되므로 인라인 함수가 길거나 인라인 함수를 여러 번 호출하는 경우 컴파일된 코드를 약간 더 크게 만들 수 있다는 것이다.

예제 )

int add(int inum1, int inum2)
{
	return inum1 + inum2;
}

void main()
{
	int result1 = add(5, 6);
	int result2 = add(3, 2);

	return;
}

add() 함수 처럼 짧은 함수는 인라인 화하기가 좋다.

inline int add(int inum1, int inum2)
{
	return inum1 + inum2;
}

void main()
{
	int result1 = add(5, 6);
	int result2 = add(3, 2);

	return;
}

다음과 같이 inline 키워드를 이용해 인라인화 하면 더 함수 호출과 관련된 오버헤드가 제거되어 더 빠르게 실행된다고 합니다.

 

class CObject
{
public:
	string		str_name;
	vector<int> vec_pos;

public:
	void SetName(string _strname) { str_name = _strname; }
	string GetName() { return str_name; }

	void SetPos(vector<int> _vecPos) { vec_pos = _vecPos; }
	vector<int> GetPos() { return vec_pos; }

public:
	CObject();
	~CObject();
};

또한, MSDN의 내용을 살펴보면 클래스 선언의 본문에 정의된 함수는 암시적으로 인라인 함수라고 한다.

컴파일을 수행할 때 컴파일러가 판단해 자체적으로 인라인 함수로 처리해줍니다.

예를 들어 해당 주소가 사용되거나 컴파일러가 너무 크다고 판단되는 경우 함수를 인라인으로 처리하지 않습니다.

 

이처럼 현대 컴파일러는 자동으로 함수를 인라인화 하는 데 매우 뛰어나기 때문에 직접 inline 키워드를 사용해 작성하지 않아도 된다고 합니다.

 

2. 함수 오버로딩(Function Overloading)

매개 변수의 개수나 타입이 서로 다른 동일한 이름의 함수들을 선언할 수 있게 한다.

예시 몇 가지를 살펴보겠습니다.

int add(int inum1, int inum2)
{
	return inum1 + inum2;
}

int add(int inum1, int inum2, int inum3)
{
	return inum1 + inum2 + inum3;
}

float add(float fnum1, float fnum2)
{
	return fnum1 + fnum2;
}


// 아래처럼 반환 값이 다르지만, 인자로 받는 매개 변수의 자료형이
// 일치하기 때문에 함수를 오버로딩 할 수 없다.
float add(int inum1, int inum2)
{
	return inum1 + inum2;
}

// 자료형을 재정의 해주어도 새로 만든 자료형이 아닌
// 또 다른 이름을 추가한 것과 같기 때문에 함수 오버로딩이 불가능하다.
typedef float FLOAT;
float add(FLOAT fnum1, FLOAT fnum2)
{
	return fnum1 + fnum2;
}

컴파일러는 함수 호출에 사용된 인자를 기반으로 호출할 add() 함수를 결정할 수 있습니다. (매개 변수의 수가 달라도 가능합니다.)

따라서 add() 함수가 고유한 매개 변수를 가지고 있는 한 원하는 만큼 많은 오버로드된 add() 함수를 정의할 수 있습니다.

 

주의할 점은 반환 형식으로만 함수를 오버로딩 할 수 없다는 점입니다.
( 함수의 이름, 인자로 전달 받는 매개 변수가 동일하고, 반환 타입만 다를 때 불가)

(typedef(자료형 재정의)로 선언한 것은 타입을 새로 만든 것이 아니기 때문에 이 또한 불가능하다.)

 

보다 자세한 내용은 다음 글을 확인해 주시길 바랍니다.

 

함수 오버로딩과 오버라이딩

해당 글은 공부를 하면서 적은 글이기 때문에 틀릴 수 있습니다. 참고용으로만 봐주세요~ 1. 오버로딩(Overloading) 오버로딩은 같은 이름의 함수에 매개 변수를 다르게 사용하여 매개 변수에 따라

rozentea.tistory.com

 

3. 디폴트 매개 변수(Default  Parameter)

매개 변수에 값이 전달되지 않는 경우, 디폴트 값이 전달되도록 함수를 선언할 수 있다.

int add(int inum1 = 0, int inum2 = 10)
{
	return inum1 + inum2;
}

void main()
{
	int result0 = add(5, 10); 	// 15가 출력된다.
    int result1 =add(2);		// 12가 출력된다.
    int result3 =add();			// 10이 출력된다.
    
    return;
}

// 다음과 같이 함수를 오버로딩 할 수도 있다.
int add(int inum1, int inum2)
{
	return inum1 + inum2;
}


// 하지만 이 경우
// main()의 add(5, 10), add(2)는 잘 작동하지만
// (위의 2가지의 경우 오버로딩 함수 호출 프로세스에 따라 구분이 가능하다.)
// (5와 10이 int 자료형이기 때문에 int add(int,int)를 사용하게 된다.)
// add()는 디폴트 매개 변수로 float인것, int인것 모두에 해당하기 때문에 모호성이 생겨 사용이 불가능하다.
// 즉, 오버로딩 시 모호성이 발생하지 않도록 디폴트 매개 변수를 적절히 사용해야 한다.
float add(float fnum1 = 0.f, float fnum2 = 10.f)
{
	return fnum1 + fnum2;
}

주의할 점은 아래 코드들 처럼은 디폴트 매개 변수를 사용할 수 없다는 점이다.

int add(int inum1 = 0, int inum2 = 10)
{
	return inum1 + inum2;
}

void main()
{
	// 디폴트를 사용할 부분의 왼쪽 매개변수를 비우고 사용할 수 없다.
	int result0 = add(, 10); // 틀림

	return;
}


// 디폴트 매개변수는 오른쪽부터 지정해야 한다.
// 따라서 다음은 허용되지 않는다.
int add(int inum1 = 0, int inum2) // 틀림
{
	return inum1 + inum2;
}


// 선언된 후에는 디폴트 매개 변수를 다시 선언할 수 없다.
// 즉, 전방 선언과 함수 정의가 있는 함수의 경우, 전방 선언이나 함수 정의 둘 중 하나에서 만 할 수 있다.
int add(int inum1 = 0, int inum2);
int add(int inum1 = 0, int inum2) // 함수 선언과 함수 정의 2곳에서 디폴트 매개변수를 선언해 틀림
{
	return inum1 + inum2;
}

이 처럼 디폴트 매개 변수는 좋은 기능이지만 문법에 알맞게 사용해야하고, 모호성이 발생하지 않게 작성해 주어야 한다.

 

4. 참조(Reference)

변수에 별명을 붙여 변수 공간을 같이 사용할 수 있는 참조 개념을 도입했다.

void main()
{
	int iValue = 10;
	int& refValue = iValue;
    
    return;
}

 

5. 참조에 의한 호출(Call by Reference)

함수 호출시 참조를 전달 할 수 있게 한다.

 

참조와 참조에 의한 호출에 대한 자세한 내용은 다음 글을 읽어주세요.

 

C++ 참조형 변수 (Reference variable)

해당 글은 공부를 하면서 적은 글이기 때문에 틀릴 수 있습니다. 참고용으로만 봐주세요~ 1. 참조형 변수 (Reference variable) C++에서는 일반 변수 자료형, 포인터 자료형 뿐 아니라 참조형(reference)를

rozentea.tistory.com

 

6. new와 delete 연산자

동적 메모리 할당, 해제를 위한 new, delete 연산자를 도입한다.

class CObject
{
public:
	string		str_name;
	vector<int> vec_pos;

public:
	void SetName(string _strname) { str_name = _strname; }
	string GetName() { return str_name; }

	void SetPos(vector<int> _vecPos) { vec_pos = _vecPos; }
	vector<int> GetPos() { return vec_pos; }

public:
	CObject();
	~CObject();
};



void main()
{
    CObject* Cat = new CObject;
    delete Cat;
    
    return;
}

new와 delete를 이용해 위 코드처럼 동적 메모리를 할당해 클래스 객체를 만들고, 할당 받은 메모리를 반환해 줄 수 있다.

보다 자세한 내용은 다음 글을 확인해 주시길 바랍니다.

 

C++ new와 delete/ malloc()과 free()

해당 글은 공부를 하면서 적은 글이기 때문에 틀릴 수 있습니다. 참고용으로만 봐주세요~ 0. 개요 new & delete, malloc & free는 동적할당을 하기 위해 사용되는 키워드, 함수 입니다. 동적할당이라는

rozentea.tistory.com

 

7. 연산자 재정의 (Operator Overloading)

기존의 연산자에 새로운 연산을 정의할 수 있게 한다.

 

보다 자세한 내용은 다음 글을 확인해 주시길 바랍니다.

 

함수 오버로딩과 오버라이딩

해당 글은 공부를 하면서 적은 글이기 때문에 틀릴 수 있습니다. 참고용으로만 봐주세요~ 1. 오버로딩(Overloading) 오버로딩은 같은 이름의 함수에 매개 변수를 다르게 사용하여 매개 변수에 따라

rozentea.tistory.com

 

8. 제네릭 함수의 클래스(Generics)

함수나 클래스를 데이터 타입에 의존하지 않고 일반화시킬 수 있다.

 

3. C++의 객체 지향의 특성


C++은 객체 지향 언어로서 다음과 같은 객체 지향적인 특성을 가지고 있습니다.

1) 객체와 캡슐화 (Encapsulation)

캡슐화는 데이터를 캡슐로 싸서 외부의 접근으로부터 데이터를 보호하는 객체 지향 특성이다. C++에서 캡슐의 역할을 하는 것이 클래스이며 class 키워드를 이용하여 작성한다. 클래스는 객체를 정의하는 틀이며, 객체는 클래스라는 틀에서 생겨난 실체(instance)이다. C++ 클래스는 멤버 변수들과 멤버 함수들로 이루어지며, 멤버들은 캡슐 외부에 공개하거나(public), 보이지 않게(private) 선언할 수 있다. (은닉성)

이중 공개된 멤버들만이 외부 객체들이 접근할 수 있다. C++ 프로그램 개발 시, 멤버 변수들은 외부에 보이지 않게 선언하여 외부에 노출시키지 않는 것이 좋다.

대신 일부 멤버 함수들을 외부에 공개하여, 이 멤버 함수를 통해서 멤버 변수에 간접적으로 접근하게 한다.

class CObject
{
// private, protected, public 키워드로 데이터를 공개하거나 숨길 수 있다.
private:
	// 멤버 변수
	string		str_name;
	vector<int> vec_pos;

public:
	// 멤버 함수
	void SetName(string _strname) { str_name = _strname; }
	string GetName() { return str_name; }

	void SetPos(vector<int> _vecPos) { vec_pos = _vecPos; }
	vector<int> GetPos() { return vec_pos; }

public:
	CObject();
	~CObject();
};

 

2) 상속성(Inheritance)

C++에서 상속은 객체를 정의하는 클래스 사이에 상속 관계를 두어, 하위 클래스의 객체가 생성될 때 하위 클래스에 선언된 멤버뿐 아니라 상위 클래스에 선언된 멤버들도 함께 가지고 탄생하게 한다.

상속은 구현된 코드의 재사용성을 높여서 소프트웨어 생산성을 높인다.

class CPet
{
public:
	string		str_name;
	vector<int> vec_pos;

public:
	void SetName(string _strname) { str_name = _strname; }
	string GetName() { return str_name; }

	void SetPos(vector<int> _vecPos) { vec_pos = _vecPos; }
	vector<int> GetPos() { return vec_pos; }

public:
	CPet();
	~CPet();
};

class CCat
	: public CPet
{
private:
	eCAT_TYPE	e_CatType;

public:
	void CatchMouse();

public:
	CCat();
	~CCat();
}

위 코드 처럼 CPet을 상속받아 CCat을 선언해 생성하면, CPet의 멤버 변수와 멤버 함수를 모두 지니고 있고, 본인만의 멤버 변수와 멤버 함수를 함께 지니고 생성된다.

 

3)  다형성(Polymorphism)

C++에서 다형성은 상속 관계에서도 나타난다. 예를 들어 고양이와 2가지 클래스가 CPet 클래스를 상속받는다고 할 때 CPet 클래스에 선언되어 있는 "Cry()"함수를 각각 자신의 특성에 맞게 "야옹"과 "멍"을 출력하게 만들 수 있다.

이 처럼 같은 클래스를 상속 받더라도 함수를 오버라이딩 해 각각 클래스에 더 알맞은 방식으로 재구현을 할 수 있다.

class CPet
{
public:
	string		str_name;
	vector<int> vec_pos;

public:
	void Cry() { cout << "울음소리를 낸다."; }

public:
	CPet();
	~CPet();
};

class CCat
	: public CPet
{
private:
	eCAT_TYPE	e_CatType;

public:
	void Cry() { cout << "야. 옹."; }

public:
	CCat();
	~CCat();
};

class CDog
	: public CPet
{
private:
	eDOG_TYPE	e_DogType;

public:
	void Cry() { cout << "멍! 왈! 뭉! 월!"; }

public:
	CDog();
	~CDog();
};

 

 

객체지향 프로그래밍에 대한 자세한 내용은 다음 글을 확인해주세요.

 

객체지향 프로그래밍

해당 글은 공부를 하면서 적은 글이기 때문에 틀릴 수 있습니다. 참고용으로만 봐주세요~ 1. 객체지향 프로그래밍 (Object - Oriented Programming) 개요 객체지향 프로그래밍(Object-Oriented Programming, OOP)은

rozentea.tistory.com

 

 

4. 참고 문헌


 

C++ 언어의 특징

C++ 언어의 특징 1. C++ 언어의 설계 목표 C++ 언어의 기본적인 설계 목표에 대해서 알아보자. 1) C 언어로 작성된 프로그램과의 호환성(compatbility)을 유지한다. 기존에 작성된 C 프로그램을 그대로 사

neos518.tistory.com

 

인라인 함수(C++)

C++ 인라인 키워드(keyword) 사용하여 컴파일러에 인라인 함수를 제안할 수 있습니다.

learn.microsoft.com

 

C++ 08.06 - 인라인 함수 (inline function)

인라인 함수 (inline function) 함수를 사용하면 다음과 같은 많은 이점을 얻을 수 있다. 함수 내부의 코드를 재사용할 수 있다. 인스턴트 코드보다 함수에서 코드를 변경하거나 업데이트하기가 더

boycoding.tistory.com

 

'프로그래밍 > C++' 카테고리의 다른 글

C++ 메모리 구조  (0) 2024.01.29
C++ new와 delete/ malloc()과 free()  (0) 2024.01.29
C++ 참조형 변수 (Reference variable)  (0) 2024.01.29
함수 오버로딩과 오버라이딩  (2) 2024.01.26
객체지향 프로그래밍  (4) 2024.01.24