1. 템플릿
- 임의의 데이터 타입을 위해 작성된 함수 or 클래스. 정확하게는 '정의'만 한 것이다. 예를 들어 다음과 같은 템플릿이 있다고 하자.
template <class T>
inline const T& max(const T& a, const T& b)
{
return a>b? a: b;
}
위 정의 자체는 아무런 영향을 주지 않는다. 이 정의가 유효하려면, 사용이 되어야 '컴파일타임'에 실제 함수가 만들어진다.
예)
int a = 10;
int b = 10;
max(a,b); // 위 템플릿에서 max(T, T). 여기서는 타입이 int이므로, max(int, int)함수가 생성된다.
float c = 10.1f
max(a,c); // 찾아봐도 max(T1, T2)는 존재하지 않는다. 컴파일러시 T가 모호하다고 에러를 준다.
만약 T1, T2를 지원하고 싶다면 다음과 같이 작성하여도 동작은 한다.
예제보기 접기
예)
template <class A, class B> inline const A& max2(const A& a, const B& b){ if( a>b){ return a; } else { return (A)(b); //우선 기존의 c스타일 캐스팅을 사용. } }
접기
하지만 이렇게 작성하지 말고, 멤버함수를 만들어서 처리할 수 있다. 멤버함수도 템플릿이 될 수 있다.
멤버함수템플릿예제 접기
예제)
template <class T> class MyClass2{ private: T value; public: template<class X> void assign(const MyClass2<X>& x){ //value = x.value; //이 구문문은 에러가 발생. (다른 클래스 private에 접근이 안됨) //value = x.getvalue(); //그냥 컴파일하면 워닝발생
value = static_cast<T>(x.getValue()); //워닝은 없다. x의 값을 명시적으로 T타입으로 캐스팅 } T getvalue() const{ return value; } };
void Class2Test(){ MyClass2<int> d; MyClass2<float> f; d.assign(d); d.assign(f); //동작한다 }
접기
위의 경우 컴파일 시, 경고가 발생한다. 지금 x.getvalue()에서 묵시적인 형 변환이 일어났다. 그러면서 복사본이 만들어 졌는데, 그 복사본은 assign의 범위를 가지기에 경고를 표시하여 준다.
2. 형변환 (casting)
-형변환에는 4가지가 있음.: static_cast, dynamic_cast, const_cast, reinterpret_cast
1) const_cast
const변수의 속성을 할당/제거할때 사용한다.
const_cast예제보기 접기
예제소스
//const_cast테스트
void constcastTest(){ int test = 10; const int* pTest = NULL;
pTest = const_cast<const int*>(&test); int* pTest2 = const_cast<int*>(pTest); //const int*의 const를 제외하고 int*에 넘겨줄수 있다.
//*pTest = 5; // C2166 에러 발생. 상수 object다. const이므로. *pTest2 = 5; const int* pTest3 = static_cast<const int*>(&test); //OK
const int cTest = 20; int* pcTest1 = const_cast<int*>(&cTest);
*pcTest1 = 5; //int cTest1 = const_cast<int>(cTest); //이 구문은 에러난다. }
const속성이 걸린 변수의 포인터를 가져올 수 있다. 다만, 위에 파란색으로 표시된 부분을 확인하여 보니, VC++ 12(VS2013)에서와 VC6에서의 동작이 다르다. VC6에서는 캐스팅 된 변수가 다른 저장공간에 저장되는 것으로 보여진다. 그렇지 않다면 cTest와 *pcTest의 값이 다를리 없다. 하지만 VS 2013에서 실행하니 값이 변환됨
접기
2) static_cast
기존 C스타일 캐스팅과 동일한 동작.
더보기 접기
예제
//static_cast테스트
void staticcastTest(){
char str[] = "Hello";
int test = 10;
int* pStr = NULL;
char *str2 = "hello2";
cout << static_cast<float>(test) << endl;
//cout << static_cast<int*>(&str[0]) << endl; //다른 포인터간 static_cast는 안됨
//pStr = static_cast<int*>(str2); //상동
cout << static_cast<int>(str[0]) << endl; //char <-> int 가능
}
> 다른 포인터간 캐스팅은 허용되지 않는다.
접기
3) dynamic_cast
다형성을 띄고 있는 타입을 실제타입으로 변환 (downcast) 하는 것을 가능하게 함. 단, 다형성을 가지지 않는 객체간 변환은 불가능하다. (virtual 함수로 상속받은 함수가 필요하다.) 그리고 RTTI옵션이 체크해제되어있으면 캐스팅 구문을 만났을 때, 프로그램이 비정상 종료된다.
( RTTI옵션 설정 방법은 옆의 링크 참조 : http://msdn.microsoft.com/ko-kr/library/we6hfdy0.aspx )
dynamic_cast가 더 안전한 변환을 제공하지만 포인터나 참조에서만 사용이 가능하다. (MSDN내용 발췌 : http://msdn.microsoft.com/ko-kr/library/c36yw7x9.aspx )
더보기 접기
예제
//dynamic_cast 예제
class Base{ public: virtual void TestFunc1(){ cout << "Base" << endl; } //virtual함수필요. (다형성 필요함) Base(){}; //또한 컴파일러에서 RTTI옵션 켜져야 동작 };
class Child1 : public Base{ public: void TestFunc1(){cout << "Ch1" << endl; } Child1(){}; };
//dynamic_cast테스트 void dynamiccastTest(){ int a = 10; //float *b = dynamic_cast<float*>(&a); //C2680. invalid target type을 만난다 Base *pBase = new Base; Base *pChild1 = new Child1; Child1 *pCh1 = new Child1; Child1 *pCh2 = NULL; pCh2 = dynamic_cast<Base*>(pBase); //컴파일시 에러발생 안하나, 런타임시 타입변환에 실패. (RTTI옵션 꺼져있으면 당연히 crash된다.)
pCh2 = dynamic_cast<Child1*>(pChild1); //RTTI옵션이 꺼져있으면 크래시 발생, 옵션 설정해주어야 성공. }
접기
4) reinterpret_cast
지정된 포인터를 정한 캐스팅 방식으로 다시 읽어버리는 캐스트 방식. 안정성을 보장할 수 없으므로 변환관계가 명확할 때에 사용하는 것이 좋다. 하지만 가급적 사용을 자제하는 것을 권장함.
예제보기 접기
예제
class TestOne{
public:
virtual void put1(void){ cout << "test1" << endl; }
int m_a;
int m_b;
}
class TestTwo{
public:
virtual void put1(void){ cout << "test3" << endl;}
virtual void put2(void){ cout << "test2" << endl; }
}
void main(){
TestOne* pTestOne = new TestOne;
TestTwo* pTestTwo = NULL;
pTestTwo = pTestOne; //캐스팅 안됨(컴파일 에러)
pTestTwo = dynamic_cast<TestTwo>(pTestOne); //캐스팅 안됨(컴파일 에러. 상속관계가 아님)
pTestTwo = reinterpret_cast<TestTwo>(pTestOne); // 캐스팅 된다
}
만약 여기서, pTestTwo->put2()를 호출하면 오류 발생한다. (원래 TestOne에는 put2가 없다.).
이 캐스팅은 void*로 넘어온 데이터가 어떤 데이터 타입인지 명확하게 알 수 있을 때, 사용하는 것은 괜찮다.
접기
3. 예외처리
예외가 발생하면 exception이 발생한다. (또는 프로그래머가 throw를 통해 excpetion을 던져줄 수 있다.) 예외가 발생하면 catch문을 만나거나 main함수를 빠져나갈 떄 까지, stack unwinding을 수행한다.
예외를 지정하여줄 수 있다. (exception specification). 예외를 지정하면 어느 예외가 던져질 지, 알 수 있다. 하지만 지정되지 않은 예외가 발생할 경우. terminate가 호출되어 프로그램은 종료된다. 해당 함수에서만 던져질 수 있는 예외를 지정하는 것이 아니라, try~catch구문에서 다른 함수가 호출되면, 그 함수에서 던져질 수 있는 예외도 고려해야 한다.
더보기 접기
예제)
#include <iostream>
using namespace std;
void exceptiontest()
{
int num;
cout << "input number";
cin >> num;
try{
if( num > 1000){
throw 10.2f; //float을 던진다.
}else if( num > 0){
throw 10;
} else{
throw 'm';
}
//만약 여기에 대상이 되는 catch가 없다.
//그러면 프로그램 crash
}catch(int exp){
cout << "except int" <<endl;
}catch(char exp){
cout << "except char" << endl;
}catch(...){ //이 예외처리는 모든 예외에 대해서 catch.
//편리하다는 점은 있다. 하지만 모든 예외에 대해 던져진다. 성능측면은?
// 그리고 여기에 catch되면서 메모리 누수는 없는지 고려
cout << "unknown exception" << endl;
}
}
접기
STL의 경우 기본적으로 안정성 보다는 성능 위주로 고려되었다. 그래서 상대적으로 안정성에 대한 요구가 발생되었고, 그에 따라 STL에서는 기본적으로 노드를 구성/변경하는데 있어 성공하거나 영향을 받지 않도록 설계되었다.
4. auto_ptr
스마트포인터의 한 종류. auto_ptr이 제거되면 가리키고 있는 대상객체도 메모리에서 해제된다. (소멸자를 호출하여준다.) auto_ptr은 소유권 개념이 있고, 소유권은 공유되지 않는다. 그래서 할당과정은 소유권을 이전하는 개념이다. 그래서 멤버변수로 사용될 경우. 복사생성자나 할당연산자를 지원하지 않는 클래스에서 사용가능하다. (복사나 할당시 소유권을 넘겨주므로) 또한, 레퍼런스 카운트가 없다.
레퍼런스 카운팅을 지원하고, 컨테이너에 넣을 수 있는 스마트포인터는 tr1::shared_ptr, boost::shared_ptr, Loki::SmartPtr등이 있다. ( http://gomnezip.tistory.com/316에 해당 내용 있음)
더보기 접기
예제
template<class T>
ostream& operator<< (ostream& strm, const auto_ptr<T>& p){ if( p.get() == NULL){ strm << "NULL"; // NO: print NULL } else { strm << *p; // YES: print the object } return strm; }
void autoptr1test() { auto_ptr<int> p(new int(42)); auto_ptr<int> q;
//auto_ptr<int> r = new int(42); //auto_ptr을 생성자가 explicit이어서 일반적인 포인터 할당 불가
cout << "after initialization:" << endl; cout << " p: " << p << endl; cout << " q: " << q << endl;
q = p; cout << "after assigning auto pointers:" << endl; cout << " p: " << p << endl; cout << " q: " << q << endl;
*q += 13; // change value of the object q owns p = q; cout << "after change and reassignment:" << endl; cout << " p: " << p << endl; cout << " q: " << q << endl; } 결과 after initialization p : 42 q : NULL after assigning auto pointers p : 42 // 예상되는 결과는 NULL이지만 VC6에서 실행하니 NULL이 아님 q : 42 after change and reassignment p : 55 q : 55 //예상되는 결과는 NULL이지만 VC6에서 실행하니 NULL이 아님
접기
* STL컴포넌트 용어정리
컨테이너 : 특정 타입의 원소들의 집합을 다루는데 사용함
반복자 : 객체가 소유한 원소를 순회하기 사용한다.
-> 반복자는 모든 컨테이너에 대해 동일한 인터페이스를 제공한다.
알고리즘 : 객체가 소유한 원소를 처리하기 위해 사용.
-> 각 객체를 순회하는데 있어 반복자가 사용되었다. 모든 컨테이너에 대해 공통적인 인터페이스를 제공하므로, 반복자를 사용하면 알고리즘은 하나만 존재하여도 된다.
5. STL 컨테이너
1) 컨테이너의 특징
- '값 의미론'을 제공 : 컨테이너에 원소를 삽입시, 복사본을 만들어서 가지고 있다.
- 모든 원소는 순서를 가짐 : 이 순서는 '반복자'를 이용하여 순회할 수 있다.
- 컨테이너의 종류
i) 시퀀스 컨테이너 : 순서를 가지는 컨테이너. list, vector, deque등이 있음
ii) 연관 컨테이너 : 이진트리로 구성된 컨테이너. set/multiset/map/multimap이 있다.
2) 공통적인 동작
- 복사생성자, 소멸자, 기본생성자등을 제공한다.
- 비교동작 : 일반적인 비교연산자를 사용한다. 그리고, 컨테이너의 타입이 같아야 하고, 사전방식의 비교를 사용하여 검색한다. ( 'a'와 'aa'를 비교하면 'a'가 더 작다.)
- 사용자가 컨테이너를 할당시, 컨테이너에 원소들을 복사한다. 같은 타입의 원소를 사용중이고, 소스가 더 사용되지 않으면 swap을 이용하자. (swap은 상수복잡도를 보장한다. 즉, 시간이 덜 소요된다.)
3) vector
- 벡터의 메모리 구조
- 동적배열을 가지는 컨테이너. 순서를 가지는 컬렉션이다. 랜덤액세스( []연산 )를 제공하고, 리스트의 제일 뒤에 삽입/삭제는 매우 빠르다. 그러나 중간에 삽입/삭제시 이보다 꽤 시간이 많이 소요되었다.
더보기 접기
예제보기
void vector1(){ vector<string> sentence; sentence.reserve(5); sentence.push_back("Hello"); sentence.push_back("how"); sentence.push_back("are"); sentence.push_back("you"); sentence.push_back("?");
copy(sentence.begin(), sentence.end(), ostream_iterator<string>(cout, " ")); cout << endl; cout << "maxsize:" << sentence.max_size() << endl; cout << "size:" << sentence.size() << endl << "capacity:" << sentence.capacity() << endl; swap(sentence[1], sentence[3]); print_elems(sentence, "afterswap: "); sentence.insert(find(sentence.begin(), sentence.end(), "?"), "always"); // 1) sentence.insert(find(sentence.begin(), sentence.end(), "ar"), "dummy"); // 2) sentence.back() = "!"; // 3) print_elems(sentence, "after modify:"); cout << "maxsize:" << sentence.max_size() << endl; cout << "size:" << sentence.size() << endl << "capacity:" << sentence.capacity() << endl; }
> 1) : insert할 문자열을 '?'을 찾아 그 앞에 insert한다.
2) : 만약 insert할 위치를 찾지 못하면 가장 마지막 위치에 insert한다. (find에서 end()가 호출되므로)
3) : 마지막 리스트의 내용을 "!"로 바꿔치기한다.
> 또한 capacity가 2배로 늘어났다. 여기서 capacity는 앞으로 추가 가능한 원소의 개수가 아니라 현재 할당된 용
량에서 가질 수 있는 원소의 개수를 의미 함을 알 수 있다. 그리고, 가질수 있는 원소의 개수가 부족할 경우 2배의
용량을 할당받음도 추정할 수 있다.
접기
4) deque
- vector와 비슷하지만 양방향에 대하여 개방되어 있다.
dequq의 메모리 구조
위와 같이 구성되어 있어, 재할당시 벡터보다는 효율적이다. (메모리 전체를 복사하는 것이 아니라 일부만 재할당 하면 된다.) 그리고, 원소의 엑세스와 반복자 이동시에는 벡터보다 조금 느리다.
더보기 접기
예제
void deque1(){ deque<string> col; col.assign(3, string("string")); col.push_back("last-str"); col.push_front("front-str"); print_elems(col, "init: "); col.pop_front(); col.pop_back(); for( int i = 1 ; i < col.size() ; i++){ col[i] = "another" + col[i]; //another라는 문자열이 string앞에 추가됨 (두번째부터) } print_elems(col, "add another:"); col.resize(4, "resize-str"); // size가 증가하면서 마지막에 resize-str이 추가됨 print_elems(col, "after resize:"); col.resize(2, "resized"); //resize된 곳 까지만 남기고 나머진 목록에서 사라진다 print_elems(col, "last: "); }
접기
5) 리스트
- 리스트 구성 방식
- 이중 연결 리스트처럼 구성되어 있다. 랜덤 액세스는 지원하지 않는다. 그러나 삽입/삭제의 동작은 vector나 deque에 비해 빠르다. (복사하는 과정 없이 포인터 값만 조작한다.)
6) set/multiset
- 연관컨테이너(원소가 insert시 자동으로 정렬하는 컨테이너)임. 따라서, 직접적으로 원소에 접근하여 값을 수정할 수 없다 . (직접 값을 수정할 경우 자동정렬이 깨어진다.)
- set과 multiset의 차이는, 중복 허용안함(set)과 중복허용(multiset)의 차이.
- 정렬조건에 대해 다음 두가지 방법으로 설정 가능하다.
i) 템플릿의 파라미터로 넣는 방식
예) set<int, greater<int> > col; //여기서 주의할 것은 'greater<int> >'에서 '>'와 '>'사이를 하나 띄어줄것. (아니면 컴파일 타임에 에러가 발생한다.)
ii) 생성자의 파라미터로 넣는 방식
예) set c(op); op는 이항조건연산자로, 비교하는 조건이 들어가면 된다.
*정렬조건을 별도로 설정하지 않으면 less<type>이 기본으로 선택된다.
- set/multiset에서 제공하는 특별한 검색 함수는 다음과 같다.
lower_bound, upper_bound, equal_range
더보기 접기
예제)
void set1() { set<int> c; c.insert(1); c.insert(2); c.insert(4); c.insert(7); c.insert(8); c.insert(9); cout << "lower_bound(3):" << *c.lower_bound(3) << endl; cout << "upper_bound(3):" << *c.upper_bound(3) << endl; cout << "equal_range(3):" << *c.equal_range(3).first << ","<< *c.equal_range(3).second << endl; cout << endl; cout << "lower_bound(7):" << *c.lower_bound(7) << endl; cout << "upper_bound(7):" << *c.upper_bound(7) << endl; cout << "equal_range(7):" << *c.equal_range(7).first << ","<< *c.equal_range(7).second << endl; }
> lower_bound(3)은 3이 들어갈 수 있는 자리 중 가장 앞의 자리를 찾는 것이고,
upper_bound(3)은 3이 들어갈 수 있는 자리 중 가장 뒤의 자리를 찾는 것.
equal_range는 3이 허용되는 범위를 골라준다.
접기
다음은 set을 사용한 예제
더보기 접기
예제보기
void set2(){ typedef set<int, greater<int> > IntSet;
IntSet col; col.insert(4); col.insert(1); col.insert(8); col.insert(5); col.insert(6); col.insert(2); col.insert(3);
IntSet::iterator pos; print_elems(col, "init:");
pair<IntSet::iterator, bool> stat = col.insert(4); pair<IntSet::iterator, bool> stat2 = col.insert(7); //삽입된 위치 바로 다음 위치를 반환함 cout << "insert 4 : " << stat.second << ", insert 8 : " << stat2.second << endl; pos = stat2.first; cout << "pos : " << *pos++ << ", pos++ :" << *pos << endl;
//set<int> col2(col.begin(), col.end()); //VC 6.0에서 이구문은 컴파일이 안된다.
//col2는 set<int, less<int> >인데, col은 set<int, greater<int> >라고.. set<int> col2; for( pos = col.begin() ; pos != col.end() ; pos++) { col2.insert( *pos); } copy( col2.begin(), col2.end(), ostream_iterator<int>(cout," ")); cout << endl; col2.erase( col2.begin(), col2.find(3));
int num = col2.erase(5); cout << num << "elements removed" << endl;
copy( col2.begin(), col2.end(), ostream_iterator<int>(cout, " ")); cout << endl; }
> 만약 원소 삽입시 실패하면 pair에서 bool값이 false가 되어 들어온다.
접기
set의 경우 정렬기준을 런타임시에 변경 가능하다. 다른 정렬기준을 가진 컨테이너를 생성 후, 할당하면 된다.
더보기 접기
예제
template <class T> class RuntimeCmp{ public: enum cmp_mode {normal, reverse}; private: cmp_mode mode; public: RuntimeCmp( cmp_mode m=normal) : mode(m){} bool operator() (const T& t1, const T& t2) const { return mode == normal ? t1<t2 : t2<t1; } bool operator== (const RuntimeCmp& rc){ return mode == rc.mode; } }; typedef set<int, RuntimeCmp<int> > IntSet; void fill(IntSet& obj); //prototype void setcmp(){ IntSet col; fill(col); print_elems(col, "Col1"); RuntimeCmp<int> reverse_order(RuntimeCmp<int>::reverse); IntSet col2(reverse_order); fill(col2); print_elems(col2, "Col2"); col = col2; //원소들은 복사되지 않았지만 compare기준은 변경되었다. col.insert(3); print_elems(col, "col1"); if( col.value_comp() == col.value_comp()){ cout << "same sorting" << endl; } else{ cout << "different sorting" << endl; } } void fill(IntSet& obj){ obj.insert(4); obj.insert(1); obj.insert(7); obj.insert(2); obj.insert(6); obj.insert(8); obj.insert(5); }
> set1 = set2를 하면 set2의 원소는 set1으로 복사되지 않지만 다른 값(정렬기준 mode변수값)은 복사되었다.
접기
* 함수명 뒤에 const를 붙이게 되면 해당 멤버함수가(멤버함수에만 const키워드 붙을 수 있음) 해당 객체의 멤버를 수정하지 않음을 의미함.
7) map/multimap
-set/multiset과 유사하다. 차이점은 <key,value>의 한 쌍으로 동작한다는 것이다. 따라서, key값은 수정이 불가능하지만, value값은 수정 가능하다. 그리고 map의 경우 key값의 중복을 허용하지 않고, multimap의 경우 key값의 중복을 허용한다.
map사용예제
더보기 접기
예제
void maptest(){ typedef map<string,float> StringFloatMap; StringFloatMap stocks; // create empty container stocks["BASF"] = 369.50; stocks["VW"] = 413.50; stocks["Daimler"] = 819.00; stocks["BMW"] = 834.00; stocks["Siemens"] = 842.20; StringFloatMap::iterator pos; for (pos = stocks.begin(); pos != stocks.end(); ++pos) { cout << "stock: " << pos->first << "\t price: " << pos->second << endl; } cout << endl; for (pos = stocks.begin(); pos != stocks.end(); ++pos) { // pos->first = "HK"; //map의 key는 const형이라 할당이 안된다. (컴파일시 에러) pos->second *= 2; } for (pos = stocks.begin(); pos != stocks.end(); ++pos) { cout << "stock: " << pos->first << "\t price: " << pos->second << endl; } cout << endl; stocks["Volkswagen"] = stocks["VW"]; stocks.erase("VW"); for (pos = stocks.begin(); pos != stocks.end(); ++pos) { cout << "stock: " << pos->first << "\t" << "price: " << pos->second << endl; } }
접기
8) 다른 STL컨테이너
- 사용자가 직접 작성한 컨테이너나, 기본의 배열, 스트링도 STL의 컨테이너처럼 사용가능
i) 침습적(invasive)방법
- 사용자가 STL이 필요로 하는 인터페이스를 제공하여 주면 됨
ii) 비침습적(noninvasive)방법
- 알고리즘과 컨테이너의 상호 인터페이스로 사용가능한 인터페이스를 작성하거나 제공. 예를들어, 배열의 경우 begin, end가 없으면 포인터 같은 대체 반복자를 제공
더보기 접기
예제)
void arr1(){ int col[] = {5,6,2,4,1,3}; transform( col, col+6, col, col, multiplies<int>());
sort(col+1, col+6); copy(col, col+6, ostream_iterator<int>(cout, " ")); cout << endl; }
접기
iii) wrapper방법
- 위 두 바업을 결합하여 STL컨테이너와 유사한 인터페이스를 제공하며, 내부 데이터는 은닉하는 클래스 작성
래퍼클래스 작성 예제
더보기 접기
예제)
#include <cstddef> // VC6.0에는 아래 두 타입이 없으므로 정의함. namespace std{ typedef unsigned int size_t; typedef int ptrdiff_t; } template<class T, std::size_t thesize> class carray { private: T v[thesize]; //T타입 원소의 고정된 배열 public: typedef T value_type; typedef T* iterator; typedef const T* const_iterator; typedef T& reference; typedef const T& const_reference; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; // 반복자 iterator begin() { return v; } const_iterator begin() const { return v; } iterator end() { return v+thesize; } const_iterator end() const { return v+thesize; } // 원소 직접 액세스 reference operator[](std::size_t i) { return v[i]; } const_reference operator[](std::size_t i) const { return v[i]; } // 크기 size_type size() const { return thesize; } size_type max_size() const { return thesize; } // 기존배열로 전환 T* as_array() { return v; } };
접기
해당 클래스 사용 예제
더보기 접기
예제)
#include "carray.hpp" void carr(){ carray<int, 10> a; for( unsigned i=0; i < a.size(); ++i){ a[i] = i+1; } print_elems(a, "init"); reverse(a.begin(), a.end()); //원소의 순서를 뒤집는다. print_elems(a, "rev"); transform(a.begin(), a.end(), a.begin(), negate<int>()); //원소의 내용을 수정하기 위함 print_elems(a, "transform"); }
접기
9)레퍼런스 의미론 구현
- 기본적으로 STL컨테이너는 원소의 값을 복사하여 가지고 있는 '값 의미론'을 지원한다. 하지만, 복사에 시간이 많이 소요될 경우. 원소의 값을 복사하는 것이 아니라 주소를 가지고 있으면 된다. 이를 위해서는 스마트 포인터를 사용하는것이 좋다. 그러나 auto_ptr은 소유권 관련한 문제가 있어, 컨테이너에 사용할 수 없다. 그래서 이를 지원하는 스마트 포인터를 사용하여야 한다.
더보기 접기
예제)
//cntptr.hpp #ifndef CNT_PTR_HPP #define CNT_PTR_HPP template <class T> class CountPtr{ private: T* ptr; long* count; public: explicit CountPtr(T* p =0) : ptr(p), count(new long(1)){} //포인터 복사 CountPtr(const CountPtr<T>& p) throw() : ptr(p.ptr), count(p.count){ ++*count; } ~CountPtr() throw(){ dispose();} //allocator : 새로운 value를 공유 CountPtr<T>& operator= (const ContPtr<T>& p) throw(){ if( this != &p){ dispose(); //멤버를 제거하고 ptr = p.ptr; //p의 멤버를 가져온다 count = p.count; ++*count; } return *this; } T& operator* () const throw(){ return *ptr; } T* operator-> () const throw(){ return ptr; } private: void dispose(){ if( --*count == 0){ delete count; delete ptr; } } }; #endif
접기
사용 예제
더보기 접기
예제)
#include "cntptr.hpp" using namespace std; void printCntPtr(CountPtr<int> elem){ cout << *elem << ' '; } void refer(){ static int values[] = { 3, 5, 9, 1, 6, 4 }; typedef CountPtr<int> IP; deque<IP> col1; list<IP> col2; for( int i = 0 ; i < sizeof(values)/sizeof(values[0]) ; ++i){ IP ptr(new int(values[i])); col1.push_back(ptr); col2.push_front(ptr); } for_each(col1.begin(), col1.end(), printCntPtr); cout << endl; for_each(col2.begin(), col2.end(), printCntPtr); cout << endl; *col1[2] *= *col1[2]; (**col1.begin()) *= -1; (**col2.begin()) = 0; for_each(col1.begin(), col1.end(), printCntPtr); cout << endl; for_each(col2.begin(), col2.end(), printCntPtr); cout << endl; }
접기
* 언제 어느 컨테이너를 사용할 것인가?
1) 기본적으로 벡터를 사용한다 . 벡터는 간단한 내부 자료구조를 사용하며 랜덤 액세스를 지원한다 .
2) 만약 원소를 컨테이너의 앞 / 끝에 자주 삽입 / 제거를 한다면 반드시 deque 를 사용할 것 . 또한 원소 제거
시 컨테이너의 메모리 사용량이 줄어들어야 할 때에도 deque 를 사용할 것
3) 원소의 제거 / 삽입 / 이동이 컨테이너 중간에서 빈번할 경우 list 를 고려해볼 것 . list 의 경우 삽입 삭제가
빠 르지만 , 원소 액세스시 성능상에 제약을 받음
4) 예외에 대해서 각각의 동작이 성공하거나 아무런 영향도 가지지 않는 컨테이너가 필요하다면 list 나 연
관 컨테이너를 고려할 것
5) 특정기준에 따라 원소를 자주 검색해야 하면 set/multiset 을 사용할 것 .
6) key/value 쌍을 위해서는 map/multimap 을 사용할 것
7) 연관배열이 필요하다면 map 을 사용할 것
8) 사전이 필요하다면 multimap 을 사용할 것
* 해시 테이블도 고려해볼만 함 . 그러나 해시 컨테이너는 순서가 없으므로 , 순서에 의존이 필요하다면 사
용 하지 않는편이 좋음
댓글