Child 클래스에서 대입 연산자 오버로딩에 Parent::operator(ref)를 주석처리하고 실행해보면 부모 클래스의 멤버는 대입 연산자가 작용하지 않은것을 확인할 수 있다.
인덱스 연산자
- C, C++의 배열은 '경계검사를 하지 않는다'는 단점을 가지고 있다.
- 즉 배열의 범위에 벗어난 인덱스 값을 넣어도 실행하는데 무리없이 진행이 되기 때문에, 안정성을 고려해서 배열 클래스를 만든다.
#include <iostream>
using namespace std;
class IntArray
{
private:
int* arr;
int arrLen;
public:
IntArray(int len) : arrLen(len)
{
arr = new int[len];
}
int& operator[](int index)
{
if (index < 0 || index >= arrLen)
{
cout << "Arr index out of range" << endl;
exit(1);
}
return arr[index];
}
~IntArray()
{
delete[] arr;
}
};
void main()
{
IntArray ia(3);
for(int i = 0; i <= 3; i++)
{
ia[i] = i + 1;
}
//결과 : Arr index out of range
}
결과와 같이 배열에 대한 잘못된 접근을 차단함으로써 안정성을 보장받을 수 있다.
※ 배열 클래스의 복사에 대해서..
- 배열은 저장소의 일종이고, 저장소에 저장된 데이터는 '유일성'이 보장되어야 하기 때문에, 저장소의 복사는 잘못된 일로 간주된다.
- 따라서, 복사 생성자와 대입 연산자는 private 멤버로 지정하여 복사를 원칙적으로 막는것이 좋은 선택이 되기도 한다.
//IntArray 클래스의 private에 복사 생성자와 대입 연산자를 멤버로 둔다.
IntArray(const IntArray& ref) { }
IntArray& operator=(const IntArray& ref) { }
const를 통한 배열 안정성 보장하기
- 어떠한 함수를 정의할 때, 매개변수를 const로 지정하면 해당 함수 내에서는 값의 변경이 불가능하므로 안정성을 보장받을 수 있지만, 위와 같은 코드에서는 인덱스 연산자에서 const 선언이 되어있지 않기 때문에 const로 지정된 매개변수로 받으면 컴파일 에러가 난다.
//전역함수
void ShowData(const IntArray& ref)
{
for(int i = 0; i < 3; i++)
{
cout << ref[i] << endl; //컴파일 에러 발생
}
}
- 해당 컴파일 에러를 해결하려면 const함수를 오버로딩하면 해결이 된다.
#include <iostream>
using namespace std;
class IntArray
{
private:
int* arr;
int arrLen;
IntArray(const IntArray& ref) { }
IntArray& operator=(const IntArray& ref) { }
public:
IntArray(int len) : arrLen(len)
{
arr = new int[len];
}
//배열 안에 값을 넣을 때 호출됨.
int& operator[](int index)
{
if (index < 0 || index >= arrLen)
{
cout << "Arr index out of range" << endl;
exit(1);
}
return arr[index];
}
//const 매개변수로 선언된 배열 안의 값을 찾을 때 호출된
int operator[](int index) const
{
if (index < 0 || index >= arrLen)
{
cout << "Arr index out of range" << endl;
exit(1);
}
return arr[index];
}
~IntArray()
{
delete[] arr;
}
};
void ShowData(const IntArray& ref)
{
for(int i = 0; i < 3; i++)
{
cout << ref[i] << endl;
}
}
void main()
{
IntArray ia(3);
for(int i = 0; i < 3; i++)
{
ia[i] = i + 1;
}
ShowData(ia);
//결과
//1
//2
//3
}
기존의 인덱스 연산자에서 const를 붙이는게 아닌 오버로딩을 한 이유는, 배열 멤버에 대한 저장이 불가능해지기 때문이다.
#include <iostream>
namespace A
{
void Func()
{
std::cout << "Namespace A : Func()" << std::endl;
}
}
namespace B
{
void Func()
{
std::cout << "Namespace B : Func()" << std::endl;
}
}
void main()
{
A::Func();
B::Func();
//결과
//Namespace A : Func()
//Namespace B : Func()
}
위의 예제에서 ::을 범위지정 연산자라고 한다.
namespace 중첩 예제
#include <iostream>
namespace A
{
int num = 0;
namespace B
{
int num = 1;
}
namespace C
{
int num = 2;
}
}
void main()
{
std::cout << A::num << std::endl;
std::cout << A::B::num << std::endl;
std::cout << A::C::num << std::endl;
//결과
//0
//1
//2
}
namespace 별칭 지정 예제
#include <iostream>
namespace A
{
namespace B
{
int num = 1;
}
}
void main()
{
namespace AB = A::B;
std::cout << AB::num << std::endl;
//결과
//1
}
using
- namespace에 선언된 모든것에 대해 namespace 지정의 생략을 명령한다.
- 편리함은 증가하지만 무분별하게 사용하면 충돌이 발생할 확률이 상대적으로 높아진다.
using 예제
#include <iostream>
using namespace A;
using namespace C;
namespace A
{
int num = 0;
namespace B
{
int num = 1;
}
namespace C
{
int num = 2;
}
}
void main()
{
std::cout << num << std::endl; //모호함으로 인해 에러 발생
std::cout << B::num << std::endl; //namespace A 생략 가능
std::cout << C::num << std::endl; //namespace A 생략 가능
}