C++ 심층 분석

C++ 언어 튜토리얼

튜토리얼의 초기 섹션에서는 마지막 두 모듈에서 이미 설명한 기본 자료를 다루고 고급 개념에 관한 자세한 정보를 제공합니다. 이 모듈에서는 동적 메모리와 객체 및 클래스에 관한 세부정보를 중점적으로 다룹니다. 상속, 다형성, 템플릿, 예외, 네임스페이스와 같은 고급 주제도 소개합니다. 이 내용은 고급 C++ 과정의 후반부에서 알아보겠습니다.

객체 지향 디자인

객체 지향 설계에 관한 훌륭한 튜토리얼입니다. 이 모듈의 프로젝트에 제시된 방법론을 적용합니다.

예시 3으로 알아보기

이 모듈에서는 포인터, 객체 지향 디자인, 다차원 배열, 클래스/객체를 사용해 더 많은 연습을 하는 데 중점을 둡니다. 다음 예를 살펴보세요. 좋은 프로그래머가 되기 위한 핵심은 연습, 연습, 연습이라는 사실은 아무리 강조해도 지나치지 않습니다.

연습 #1: 포인터 연습하기

포인터에 관한 추가 연습이 필요한 경우 포인터의 모든 측면을 다루고 다양한 프로그램 예를 제공하는 이 리소스를 읽어보세요.

다음 프로그램은 어떻게 출력되나요? 프로그램을 실행하지 말고 메모리 그림을 그려 출력을 확인하세요.

void Unknown(int *p, int num);
void HardToFollow(int *p, int q, int *num);

void Unknown(int *p, int num) {
  int *q;

  q = #
  *p = *q + 2;
  num = 7;
}

void HardToFollow(int *p, int q, int *num) {
  *p = q + *num;
  *num = q;
  num = p;
  p = &q;
  Unknown(num, *p);
}

main() {
  int *q;
  int trouble[3];

  trouble[0] = 1;
  q = &trouble[1];
  *q = 2;
  trouble[2] = 3;

  HardToFollow(q, trouble[0], &trouble[2]);
  Unknown(&trouble[0], *q);

  cout << *q << " " << trouble[0] << " " << trouble[2];
}

직접 출력을 확인한 후 프로그램을 실행하여 올바른지 확인합니다.

연습 #2: 클래스 및 객체 추가 연습

클래스와 객체를 자세히 연습해야 할 경우 여기에서 작은 클래스 두 개를 구현하는 방법을 안내하는 리소스를 확인할 수 있습니다. 시간을 내서 연습을 진행하세요.

연습 #3: 다차원 배열

다음 프로그램을 고려해 보세요.

const int kStudents = 25;
const int kProblemSets = 10;

// This function returns the highest grade in the Problem Set array.
int get_high_grade(int *a, int cols, int row, int col) {
  int i, j;
  int highgrade = *a;

  for (i = 0; i < row; i++)
    for (j = 0; j < col; j++)
      if (*(a + i * cols + j) > highgrade)  // How does this line work?
        highgrade = *(a + i*cols + j);
  return highgrade;
}

int main() {
 int grades[kStudents][kProblemSets] = {
   {75, 70, 85, 72, 84},
   {85, 92, 93, 96, 86},
   {95, 90, 83, 76, 97},
   {65, 62, 73, 84, 73}
 };
 int std_num = 4;
 int ps_num = 5;
 int highest;

 highest = get_high_grade((int *)grades, kProblemSets, std_num, ps_num);
 cout << "The highest problem set score in the class is " << highest << endl;

 return 0;
}

이 프로그램에는 '이 노선은 어떻게 작동하나요?'라고 표시된 줄이 있습니다. - 답을 알아낼 수 있나요? 자세한 내용은 여기를 참조하세요.

3-dim 배열을 초기화하고 3차원 값을 3개 색인 모두의 합으로 채우는 프로그램을 작성합니다. 해결 방법은 여기를 참고하세요.

연습 #4: 광범위한 OO 디자인 예

다음은 전체 프로세스를 처음부터 끝까지 진행하는 자세한 객체 지향 설계 예입니다. 최종 코드는 자바 프로그래밍 언어로 작성되지만 얼마나 많이 발전했는지에 따라 읽을 수 있습니다.

시간을 내어 이 예를 모두 살펴보시기 바랍니다. 이는 프로세스와 이를 지원하는 디자인 도구를 잘 보여주는 예시입니다.

단위 테스트

소개

테스트는 소프트웨어 엔지니어링 프로세스에서 중요한 부분입니다. 단위 테스트는 특정한 종류의 테스트로, 소스 코드로 구성된 단일 소규모 모듈의 기능을 확인합니다.단위 테스트는 항상 엔지니어가 실행하며 일반적으로 모듈 코딩과 동시에 실행됩니다. Composer 및 Database 클래스를 테스트하는 데 사용한 테스트 드라이버는 단위 테스트의 예입니다.

단위 테스트의 특성은 다음과 같습니다. 그들은...

  • 구성요소를 개별적으로 테스트
  • 결정론적
  • 단일 클래스에 매핑되며
  • 데이터베이스, 파일, 네트워크와 같은 외부 리소스에 대한 종속 방지
  • 빠르게 실행
  • 어떤 순서로든 실행할 수 있음

대규모 소프트웨어 엔지니어링 조직의 단위 테스트를 위한 지원 및 일관성을 제공하는 자동화된 프레임워크 및 방법론이 있습니다. 정교한 오픈소스 단위 테스트 프레임워크가 몇 가지 있으며, 이에 관해서는 이 강의의 뒷부분에서 알아봅니다. 

단위 테스트의 일부로 발생하는 테스트는 아래에 나와 있습니다.

이상적인 환경에서는 다음을 테스트합니다.

  1. 모듈 인터페이스는 정보가 올바르게 유입 및 유출되는지 확인하기 위해 테스트됩니다.
  2. 로컬 데이터 구조는 데이터가 올바르게 저장되는지 확인하기 위해 검토됩니다.
  3. 경계 조건은 처리를 제한하거나 제한하는 경계에서 모듈이 올바르게 작동하는지 테스트됩니다.
  4. 모듈을 통해 독립적인 경로를 테스트하여 각 경로와 모듈의 각 문이 한 번 이상 실행되는지 확인합니다. 
  5. 마지막으로 오류가 올바르게 처리되는지 확인해야 합니다.

코드 적용 범위

실제로 테스트로는 완전한 '코드 적용 범위'를 얻을 수 없습니다. 코드 적용 범위는 소프트웨어 시스템의 테스트 사례 모음에서 실행 (커버리지)한 부분과 실행되지 않은 부분을 확인하는 분석 방법입니다. 100% 적용 범위를 달성하려고 하면 실제 코드를 작성하는 것보다 단위 테스트를 작성하는 데 더 많은 시간이 걸립니다. 다음과 같은 모든 독립된 경로에 단위 테스트를 생각해 보세요. 이 문제는 금방 기하급수적 문제가 될 수 있습니다.

이 다이어그램에서 빨간색 선은 테스트되지 않으며 색상이 지정되지 않은 선은 테스트됩니다.

100% 적용 범위를 시도하는 대신 모듈이 제대로 작동하고 있다는 확신을 주는 테스트에 집중합니다. Google Play에서는 다음을 테스트합니다.

  • null 사례
  • 범위 테스트(예: 양수/음수 값 테스트)
  • 특이 사례
  • 실패 사례
  • 대부분의 시간 동안 실행될 가능성이 가장 높은 경로 테스트

단위 테스트 프레임워크

대부분의 단위 테스트 프레임워크는 경로 실행 중에 어설션을 사용하여 값을 테스트합니다. 어설션은 조건이 true인지 확인하는 문입니다. 어설션의 결과는 성공, 치명적이지 않은 실패 또는 치명적인 실패일 수 있습니다. 어설션이 실행된 후 결과가 성공이거나 심각하지 않은 실패이면 프로그램이 정상적으로 계속됩니다. 치명적인 오류가 발생하면 현재 함수가 취소됩니다.

테스트는 상태를 설정하거나 모듈을 조작하는 코드로 구성되며, 예상 결과를 확인하는 여러 어설션과 결합되어 있습니다. 테스트의 모든 어설션이 성공하면(예: true 반환) 테스트가 성공하고, 그렇지 않으면 실패합니다.

테스트 사례에는 하나 이상의 테스트가 포함됩니다. Google에서는 테스트를 테스트된 코드의 구조를 반영하는 테스트 사례로 그룹화합니다. 이 과정에서는 CPPUnit을 단위 테스트 프레임워크로 사용합니다. 이 프레임워크를 사용하면 C++로 단위 테스트를 작성하고 자동으로 실행하여 테스트의 성공 또는 실패에 관한 보고서를 제공할 수 있습니다.

CPPUnit 설치

SourceForge에서 CPPUnit 코드를 다운로드합니다. 적절한 디렉터리를 찾아 tar.gz 파일을 여기에 배치합니다. 그런 다음 Linux, Unix에서 적절한 cppunit 파일 이름으로 대체하여 다음 명령어를 입력합니다.

gunzip filename.tar.gz
tar -xvf filename.tar

Windows에서 작업하는 경우 tar.gz 파일을 추출하는 유틸리티를 찾아야 할 수 있습니다. 다음 단계는 라이브러리를 컴파일하는 것입니다. cppunit 디렉터리로 변경합니다. 구체적인 지침을 제공하는 INSTALL 파일이 있습니다. 일반적으로 다음을 실행해야 합니다.

./configure
make install

문제가 발생하면 INSTALL 파일을 참고하세요. 라이브러리는 일반적으로 cppunit/src/cppunit 디렉터리에 있습니다. 컴파일이 작동하는지 확인하려면 cppunit/examples/simple 디렉터리로 이동하여 'make'를 입력합니다. 모든 것이 정상적으로 컴파일되면 설정이 완료된 것입니다.

여기에서 우수한 가이드를 확인할 수 있습니다. 이 튜토리얼을 진행하면서 복소수 클래스 및 관련 단위 테스트를 만드세요. cppunit/examples 디렉터리에는 몇 가지 추가 예가 있습니다.

해야 하는 이유

단위 테스트는 몇 가지 이유로 업계에서 매우 중요합니다. 코드를 개발하는 동안 작업을 확인할 방법이 필요하다는 한 가지 이유는 이미 잘 알고 계실 것입니다. 매우 작은 프로그램을 개발하더라도 프로그램이 예상대로 작동하는지 확인하기 위해 본능적으로 일종의 검사기 또는 드라이버를 작성합니다.

오랜 경험을 통해 엔지니어는 프로그램이 첫 번째 시도에서 작동할 가능성이 매우 낮다는 것을 알고 있습니다. 단위 테스트는 테스트 프로그램이 자체 검사되고 반복 가능하도록 함으로써 이 아이디어를 기반으로 합니다. 어설션은 출력을 수동으로 검사하는 대신 사용됩니다. 또한 결과를 쉽게 해석할 수 있기 때문에 (테스트가 통과하거나 실패함) 테스트를 반복해서 실행할 수 있으므로 코드의 변경에 대한 복원력을 높여주는 안전망을 확보할 수 있습니다.

이를 구체적으로 표현해 보겠습니다. 완성된 코드를 CVS에 처음 제출하면 완벽하게 작동합니다. 한동안은 완벽하게 작동합니다. 그러던 중 어느 날 다른 사람이 내 코드를 변경하면 조만간 누군가 코드를 해킹하게 될 수 있습니다. 부모님이 알아차릴 거라고 생각하시나요? 가능성 낮음 하지만 단위 테스트를 작성하면 매일 자동으로 테스트를 실행할 수 있는 시스템이 존재합니다. 이를 지속적 통합 시스템이라고 합니다. 엔지니어 X가 코드를 부과하면 시스템에서 엔지니어가 문제를 수정할 때까지 악성 이메일을 보냅니다. 엔지니어 X가 당신이라 해도요.

소프트웨어 개발을 지원하고 변경되더라도 소프트웨어를 안전하게 유지하는 것 외에도 단위 테스트는 다음과 같습니다.

  • 실행 가능한 사양과 코드와 동기화된 상태를 유지하는 문서를 만듭니다. 즉, 단위 테스트를 읽고 모듈이 지원하는 동작을 알아볼 수 있습니다.
  • 요구사항을 구현에서 분리하는 데 도움이 됩니다. 외부에 표시되는 동작을 어설션하고 있으므로 동작 구현 방법에 관한 아이디어를 혼합하는 대신 명시적으로 생각해 볼 수 있습니다.
  • 실험을 지원합니다. 모듈의 동작이 손상되었을 때 알림을 받을 수 있는 안전망이 있다면 다양한 시도를 하고 디자인을 재구성할 가능성이 높습니다.
  • 디자인 개선 철저한 단위 테스트를 작성하려면 코드를 더 쉽게 테스트해야 하는 경우가 많습니다. 테스트 가능한 코드는 테스트할 수 없는 코드보다 더 모듈화된 경우가 많습니다.
  • 높은 품질을 유지합니다. 중요한 시스템의 사소한 버그로 인해 회사는 수백만 달러를 잃을 수 있으며 더 나쁜 경우 사용자의 행복이나 신뢰를 잃을 수 있습니다. 단위 테스트에서 제공하는 안전망으로 인해 이러한 가능성이 줄어듭니다. 또한 버그를 조기에 찾아내어 품질보증팀이 명백한 장애를 보고하는 대신 더 정교하고 어려운 장애 시나리오에 더 많은 시간을 할애할 수 있도록 합니다.

Composer 데이터베이스 애플리케이션에 CPPUnit을 사용하여 단위 테스트를 작성해 봅니다. 도움이 필요한 경우 cppunit/examples/ 디렉터리를 참고하세요.

Google의 작동 방식

소개

중세의 한 승려가 수도원의 자료실에 있는 수천 권의 필사본을 보고 있다고 상상해 보세요."아리스토텔레스의 작품은 어디에 있는 거야..."

수도원 도서관

다행히도 원고는 내용별로 정리되어 있으며 각 원고에 포함된 정보를 쉽게 검색할 수 있도록 특수 기호로 새겨져 있습니다. 이러한 조직 없이는 관련 원고를 찾기가 매우 어렵습니다.

대규모 컬렉션에서 작성된 정보를 저장하고 검색하는 활동을 정보 검색 (IR)이라고 합니다. 이러한 활동은 수 세기에 걸쳐 특히 종이 및 인쇄기와 같은 발명품과 함께 점점 더 중요해지고 있습니다. 예전에는 소수의 사람만 사용했습니다. 하지만 지금은 수억 명의 사용자가 매일 검색엔진을 사용하거나 데스크톱을 검색할 때 정보를 검색하고 있습니다.

정보 검색 시작하기

모자 속 고양이

닥터 수스는 30년 동안 46권의 아동 도서를 저술했습니다. 그의 책에서는 고양이, 소, 코끼리, 누가 누군지, 찡그린 웃음, 우스꽝스러워하는 말을 했다. 어떤 스토리에 어떤 생물이 등장했는지 기억하시나요? 부모가 아닌 경우 어린이만 닥터 수스 이야기에 이 생명체가 등장하는 것을 확인할 수 있습니다.

(COW 및 BEE) 또는 CROWS

이 문제를 해결하기 위해 몇 가지 기존 정보 검색 모델을 적용할 예정입니다.

확실한 접근 방식은 막무가내식입니다. 닥터 수스의 이야기 46개를 모두 읽고 읽어 보세요. 각 도서에 대해 COW 및 BEE라는 단어가 포함된 도서를 확인하고 동시에 CROWS라는 단어가 포함된 도서를 찾습니다. 컴퓨터는 우리보다 훨씬 더 빠릅니다. Dr. Seuss 도서의 모든 텍스트가 디지털 형식(예: 텍스트 파일)인 경우 파일을 grep하여 실행하면 됩니다. 닥터 수스의 책과 같은 소규모 컬렉션에는 이 방법이 적합합니다.

그러나 더 많은 것이 필요한 상황이 많이 있습니다. 예를 들어 현재 온라인에 있는 모든 데이터의 수집은 너무 커서 grep에서 처리할 수 없습니다. 또한 조건과 일치하는 문서를 원할 뿐만 아니라 관련성에 따라 문서를 매기는 데 익숙해졌습니다.

grep 외의 또 다른 접근 방식은 검색을 수행하기 전에 컬렉션에 있는 문서의 색인을 만드는 것입니다. IR의 색인은 교과서 뒷면의 색인과 유사합니다. 각 닥터 수스 이야기의 모든 단어 (또는 용어) 목록을 만듭니다. 이때 'the', 'and', 기타 접속사, 전치사 등의 단어는 제외하세요(이러한 단어를 불어라고 함). 그런 다음 Google은 용어를 쉽게 찾고 용어가 포함된 스토리를 식별하는 방식으로 이 정보를 표현합니다.

가능한 표현 중 하나는 상단에 걸쳐 있는 스토리와 각 행에 나열된 용어를 포함하는 행렬입니다. 열에 '1'이 표시되면 검색어가 해당 열의 스토리에 나타난다는 의미입니다.

도서 및 단어 표

각 행이나 열을 비트 벡터로 볼 수 있습니다. 행의 비트 벡터는 용어가 나타나는 뉴스의 종류를 나타냅니다. 열의 비트 벡터는 스토리에 표시되는 용어를 나타냅니다.

원래 문제로 돌아가기:

(COW 및 BEE) 또는 CROWS

이러한 항의 비트 벡터를 가져와서 먼저 비트 단위로 AND를 수행한 다음 그 결과에 대해 비트 단위 OR을 수행합니다.

(100001 및 010011) 또는 000010 = 000011

답: 'Mr. Brown Can Moo! 'Can You?', 'The Lorax'가 있습니다. 이 그림은 '일치검색' 모델인 부울 검색 모델을 보여줍니다.

닥터 수스 스토리와 스토리의 모든 관련 용어를 포함하도록 행렬을 확장한다고 가정해 보겠습니다. 행렬이 상당히 확장되며 중요한 관찰값은 대부분의 항목이 0이 된다는 것입니다. 행렬은 색인을 가장 잘 표현하는 것은 아닐 수 있습니다. 1만 저장할 방법을 찾아야 해요.

일부 개선사항

이 문제를 해결하기 위해 IR에서 사용되는 구조를 역 색인이라고 합니다. Google에서는 용어 사전을 보관하고 각 용어마다 해당 용어가 포함된 문서를 기록하는 목록을 만듭니다. 이 목록을 게시 목록이라고 합니다. 단일 연결 목록은 아래와 같이 이 구조를 나타내는 데 효과적입니다.

연결 목록에 익숙하지 않다면 Google에서 'C++의 연결된 목록'을 검색해 보세요. 연결 목록을 만드는 방법과 연결 목록을 사용하는 방법을 설명하는 많은 리소스를 찾을 수 있습니다. 이후 모듈에서 이에 대해 더 자세히 다루겠습니다.

스토리의 이름 대신 문서 ID (DocIDs)를 사용합니다. 또한 쿼리 처리를 용이하게 하기 때문에 이러한 DocID를 정렬합니다.

쿼리는 어떻게 처리할까요? 원래 문제의 경우 먼저 COW 게시물 목록을 찾은 다음 BEE 게시물 목록을 찾습니다. 그런 다음 둘을 '병합'합니다.

  1. 두 목록의 마커를 관리하고 두 게시물 목록을 동시에 살펴봅니다.
  2. 각 단계에서 두 포인터가 가리키는 DocID를 비교합니다.
  3. 두 DocID가 동일한 경우 해당 DocID를 결과 목록에 넣고 그렇지 않으면 포인터가 더 작은 docID를 가리키도록 진행합니다.

반전 색인을 만드는 방법은 다음과 같습니다.

  1. 관심 있는 각 문서에 DocID를 할당합니다.
  2. 각 문서에서 관련 용어를 식별합니다 (토큰화).
  3. 검색어마다 검색어, 용어가 발견된 DocID, 해당 문서의 빈도로 구성된 레코드를 만듭니다. 두 개 이상의 문서에 나타나는 특정 용어에는 레코드가 여러 개 있을 수 있습니다.
  4. 검색어를 기준으로 레코드를 정렬합니다.
  5. 한 용어에 대한 단일 레코드를 처리하고 둘 이상의 문서에 나타나는 검색어의 여러 레코드를 결합하여 사전과 게시물 목록을 만듭니다. DocID의 연결된 목록을 만듭니다 (정렬된 순서로). 각 항에는 한 항의 모든 레코드에서 빈도의 합계인 빈도도 있습니다.

프로젝트

실험할 수 있는 긴 일반 텍스트 문서를 여러 개 찾습니다. 이 프로젝트는 위에서 설명한 알고리즘을 사용하여 문서에서 반전 색인을 만드는 것입니다. 또한 쿼리 입력을 위한 인터페이스와 쿼리를 처리하기 위한 엔진도 만들어야 합니다. 포럼에서 프로젝트 파트너를 찾을 수 있습니다.

이 프로젝트를 완료하기 위한 가능한 프로세스는 다음과 같습니다.

  1. 가장 먼저 해야 할 일은 문서에서 용어를 식별하기 위한 전략을 정의하는 것입니다. 생각할 수 있는 모든 불용어의 목록을 만들고 파일의 단어를 읽고 용어를 저장하며 불용어를 제거하는 함수를 작성합니다. 반복에서 용어 목록을 검토할 때 목록에 불용어를 더 추가해야 할 수도 있습니다.
  2. CPPUnit 테스트 사례를 작성하여 함수를 테스트하고 makefile을 작성하여 빌드에 필요한 모든 것을 가져옵니다. 특히 파트너와 협력하는 경우 파일을 CVS에 체크인합니다. 원격 엔지니어에게 CVS 인스턴스를 여는 방법을 알아봐야 할 수도 있습니다.
  3. 위치 데이터를 포함하도록 처리를 추가합니다. 즉, 파일에서 어떤 파일과 용어가 있는 위치를 포함하나요? 페이지 번호나 단락 번호를 정의하는 계산을 알아야 할 수 있습니다.
  4. CPPUnit 테스트 사례를 작성하여 이 추가 기능을 테스트합니다.
  5. 역 색인을 만들고 위치 데이터를 각 용어의 레코드에 저장합니다.
  6. 테스트 사례를 더 작성합니다.
  7. 사용자가 쿼리를 입력할 수 있는 인터페이스를 설계합니다.
  8. 위에서 설명한 검색 알고리즘을 사용하여 반전 색인을 처리하고 사용자에게 위치 데이터를 반환합니다.
  9. 이 마지막 부분에 대한 테스트 사례도 포함해야 합니다.

다른 모든 프로젝트에서와 마찬가지로 포럼과 채팅을 통해 프로젝트 파트너를 찾고 아이디어를 공유하세요.

추가 기능

많은 IR 시스템에서 일반적으로 사용되는 처리 단계를 어간 추출이라고 합니다. 어간 추출의 주요 아이디어는 '검색'에 관한 정보를 검색하는 사용자가 '검색', '가져온', '검색' 등이 포함된 정보가 있는 문서에도 관심이 있다는 것입니다. 시스템은 잘못된 파생어로 인해 오류가 발생하기 쉬우므로 이 작업은 매우 까다롭습니다. 예를 들어 '정보 검색'에 관심이 있는 사용자는 파생어로 인해 '골든 리트리버에 관한 정보'라는 제목의 문서를 받을 수 있습니다. 어간 추출에 유용한 알고리즘은 포터 알고리즘입니다.

애플리케이션: 어디서나 가능!

Panoramas.dk에서 이러한 개념을 적용한 내용을 확인해 보세요.