항목 3 : 배열과 다형성은 같은 수준으로 놓고 볼 것이 아니다

상속성이 주는 가장 중요한 혜택 중 하나는, 기본 클래스 객체의 포인터나 참조자로 파생 클래스를 조작할 수 있다는 점이다. 이외에, 파생 클래스 객체의 배열을 기본 클래스 포인터나 참조자를 통해 조작하는 것도 가능하다. 하지만 실제 동작은 여러분이 바랬던 것이 아닐수 있다. 다음 예제를 보자.

class Base {...};
class Child : public Base {...};
void print(const Base arr[], int len)
{
    // for ( int i = 0; i < len; i++ )
    // arr[i]->xx();  
}
Base arr1[10]; 
print(arr1, 10); // 잘 동작한다.
Child arr2[10]; 
print(arr2, 10); // 컴파일러는 문제가 없다. 하지만, 원하지 않는 동작을 일으킨다. 

배열은 배열의 첫 요소를 가리키는 포인터이다. array+i가 가리키는 메모리 위치는 배열이 가리키는 위치로 부터 얼마나 떨어져 있는가도 간단한 공식으로 알아 낼 수 있다. i * sizeof(배열내의 요소객체 하나). 컴파일러는 배열 내의 요소 위치를 정확히 지정하는 코드를 생성하기 위해 배열에 담기는 요소 객체의 크기를 결정 할 수 있어야 한다. 위의 예제에서 매개변수가 array가 Base로 설정이 되어 있다. 때문에 컴파일러 입장에서는 i * sizeof(Base)로 계산 할 수 밖에 없다. 보통의 경우에 Child 객체는 Base객체 보다 클 경우가 많기 때문에 Child 객체의 배열을 넘길 경우 내부적으로 잘못된 위치를 참조하여 어떤 사태가 발생할 지 모른다.
다형성과 포인터 산술 연산은 주먹구구식으로 간단히 섞이는 성질의 것이 아니다. 배열 연산에는 거의 항상 포인터 산술 연산이 따라다니기 때문에, 배열과 다형성은 물과 기름의 관계라는 것을 숙지하도록 한다.
사실 어떤 구체 클래스로 부터 또 다른 구체 클래스를 파생시키는 일만 없으면, 객체의 배열을 다형적으로 조작하는 실수는 별로 하지 않을 것이다. 구체 클래스를 상속하여 구체 클래스를 만들지 않게끔 소프트웨어를 설계 했을 때에 장점을 항목33