스토리텔링 개발자

[Effective STL] 16. C API로 컨테이너 전달 본문

개발/Effective STL

[Effective STL] 16. C API로 컨테이너 전달

김디트 2024. 11. 25. 11:03
728x90

항목 16. 기존의 C API에 vector와 string을 넘기는 방법을 알아두자

 

 

 

C API
  • 배열과 char*를 사용하는 기존 C API들은 여전히 통용되고 있다.
  • 이들의 사용을 아예 배제할 순 없기 때문에 넘기는 방법은 알아두는 것이 좋다.

 

 

 

배열 형태로 변환 방법
vector<int> v;
&v[0]; // 배열 형태로 변환

string s;
s.c_str(); // char* 형태로 변환

 

 

 

 

배열(포인터)로 바꿀 시 주의점
  • 빈 벡터를 배열로 변경하려 할 시 주의해야 한다.
    • void doSomething(const int* pInts, size_t numInts);
      
      vector<int> v;
      ...
      doSomething(&v[0], v.size());
      // 헌데, 만약 v가 빈 벡터라면?
      // &v[0]은 있지도 않은 메모리의 주소값을 가져오려 한다.(미정의 동작)
      // 그러므로 아래 코드처럼 예외 처리를 해주어야 한다.
      if(!v.empty())
      {
          doSomething(&v[0], v.size());
      }
  • begin()로 포인터를 가져오려 하지 말자.
    • vector의 begin()이 반환하는 반복자는 포인터 일수도, 아닐 수도 있다.(항목 50 참조)
    • begin()의 반환 타입은 반복자이지, 포인터가 아니므로 이로 포인터를 대체하려 하지 말자.
    • // 어쩔 수 없이 써야 한다면 아래처럼 사용하자.
      // &v[0]과 똑같은 포인터를 만드는 표현식이다.
      &*v.begin()
  • vector의 포인터를 얻는 방법은, string에는 통하지 않는다.
    1. string의 데이터 자체가 연속 메모리에 저장되도록 규정되어 있지 않다.
    2. string의 내부 문자열 값이 널 문자로 끝나지 않는다.
    3. 그렇기에 c_str 멤버 함수가 있는 것이다.
  • string의 경우 빈 문자열에도 문제 없이 동작한다.
    • void doSomething(const char* pString);
      
      string s;
      ...
      doSomething(s.c_str()); // 빈 문자열인지 체크할 필요가 없다.
      // 빈 문자열일 경우 c_str()은 널 문자 포인터를 반환한다.
  • vector나 string가 전달한 포인터의 값을 변경해서는 안된다.
    • void doSomething(const int* pInts, size_t numInts);
      void doSomething(const char* pString);
      // 매개변수 pInts와 pString은 모두 const이다.
    • string의 경우 절대로 값을 변경하면 안된다.
      • c_str이 문자열 데이터의 내부 포인터를 반환한다는 보장이 없기 때문이다.
    • vector의 경우 약간의 융통성이 있다.
      • 요소를 변경하는 것은 대개 무난하게 넘어가지만..
      • 벡터의 요소 개수(크기)를 바꾸려고 하면 절대로 안된다.
      • 정확한 크기를 파악할 수 없게 되면서, v의 내부 데이터가 일관성을 잃게 된다.
      • 혹여 크기와 용량이 같은 벡터(항목 14 참조)에 요소를 추가한다거나 하면....
    • 어떤 vector의 경우 데이터에 대해 특수한 제약을 가지고 있을 수도 있다.
      • 그 특수 제약을, C API 내에서도 지켜줘야 한다.

 

 

 

vector 객체를 C API를 통해 초기화하기
// 최대 arraySize 크기의 double 배열에 대한 포인터를 받아,
// 그 배열에 특정 데이터를 기록한다.
// 기록한 특정 데이터 갯수를 리턴하며, 그 크기는 arraySize를 넘기지 않는다.
size_t fillArray(double* pArray, size_t arraySize);

vector<double> vd(maxNumDoubles);
vd.resize(fillArray(&vd[0], vd.size()));
  • 이는 vector에만 통한다.
    • C/C++ 배열과 동일한 메모리 배열 구조를 가진 컨테이너는 vector가 유일하기 때문이다.
  • 그렇지만, string도 방법이 없는 것은 아니다.
// 위 함수와 같지만, 특정 char를 삽입하는 함수
size_t fillString(char* pArray, size_t arraySize);

// char 벡터를 생성하여 C API를 사용한다.
vector<char> vc(maxNumChars);
size_t charsWritten = fillString(&vc[0], vc.size());

// vector를 string으로 변환한다.
string s(vc.begin(), vc.begin() + charsWritten);
  • 이 아이디어는 다른 컨테이너에도 모두 통용 가능하다.
size_t fillArray(double* pArray, size_t arraySize);

vector<double> vd(maxNumDoubles);
vd.resize(fillArray(&vd[0], vd.size()));

deque<double> d(vd.begin(), vd.end()); // 덱
list<double> l(vd.begin(), vd.end()); // 리스트
set<double> s(vd.begin(), vd.end()); // 셋
  • 이 아이디어를 반대로 적용하여, 다른 STL 컨테이너 데이터를 C API에 넘길수도 있을 것이다.
void doSomething(const int* pInts, size_t numInts);

set<int> intSet;
...
vector<int> v(intSet.begin(), intSet.end()); // 벡터로 변경 후
if(!v.enpty())
{
    doSomething(&v[0], v.size()); // 포인터 전달
}

 

728x90
Comments