며칠 전, 한 블로거는 생성된 실행 파일에서 코드가 너무 커진다는 이유로 C++의 제네릭을 비판하는 기사를 읽었습니다.
이 블로거는 수년간 C++ 소프트웨어 개발에 종사해 왔으며 이전 개발 환경은 리소스가 충분한 서버였기 때문에 디스크 공간 문제를 고려할 필요가 없었습니다. 최근에는 스마트홈 호스트의 임베디드 플랫폼 개발에 C++를 사용할 계획입니다. FLASH 저장 공간은 제한되어 있으므로 반드시 고려하고 주의해야 하는 요소입니다.
다음과 같이 요소 유형이 서로 다른 두 개의 목록을 정의합니다.
list<int> l1; list<string> l2;
C 언어를 사용하는 경우 어떻게 해야 합니까? 이는 list
다음은 list
//! code-1struct list_int_item { int value; struct list_int_item *next; };struct list_int { struct list_int_item *head; size_t size; }; void list_int_insert(struct list_int *p, int value);int list_int_sort(struct list_int *p);bool list_int_empty(struct list_int *p); ...
다음은 list
//! code-2 struct list_string_item { string value; struct list_string_item *next; }; struct list_string { struct list_string_item *head; size_t size; }; void list_string_insert(struct list_int *p, string value); int list_string_sort(struct list_int *p); bool list_string_empty(struct list_int *p); ...
둘 다 차이점은 유형입니다. C 언어에서는 다음과 같이 매크로를 사용하여 유형을 대체합니다.
//! code-3 #define LIST_DECLARE(TYPE) \ struct list_##TYPE##_item { \ TYPE## value; \ struct list_##TYPE##_item *next; \ }; \ \ struct list_##TYPE { \ struct list_##TYPE##_item *head; \ size_t size; \ }; \ \ void list_##TYPE##_insert(struct list_##TYPE *p, ##TYPE## value); \ int list_##TYPE##_sort(struct list_##TYPE *p); \ bool list_##TYPE##_empty(struct list_##TYPE *p); \ ...
그런 다음 list
//! code-4 LIST_DECLARE(double)
따라서 제네릭이 중복 코드를 생성하는 것은 불가피합니다. 적어도 C를 사용하여 그러한 제네릭을 수행하는 것은 불가피합니다.
어쩔 수 없는 일이니 위의 문제를 최대한 피하는 방법에 대해 알아보겠습니다. "Effective C++"에는 다음과 같이 구체적으로 언급하는 장이 있습니다. 템플릿에 불필요한 매개변수를 사용하지 마십시오. 왜냐하면 컴파일러는 각기 다른 매개변수에 대해 해당 코드 세트를 생성하기 때문입니다.
코드에 데이터 유형이 하나만 있으면 해당 유형으로 여러 변수가 정의되어 있어도 컴파일러는 관련 코드 세트를 하나만 생성합니까? (이렇게되어야합니다).
비교예를 작성하세요: (불필요한 코드 생략)
test1.cpp, map
//! code-5 map<int, string> m1; map<int, string> m2; map<int, string> m3; m1.insert(std::make_pair(1, "hello")); m2.insert(std::make_pair(1, "hi")); m3.insert(std::make_pair(1, "lichunjun"));
test2.cpp는 test1.cpp와 비교하면 세 가지 유형이 있습니다.
//! code-6 map<int, string> m1; map<int, double> m2; map<int, int> m3; m1.insert(std::make_pair(1, "hello")); m2.insert(std::make_pair(1, 1.2)); m3.insert(std::make_pair(1, 44));
결과적으로 컴파일된 실행 파일 크기 비교는
[hevake_lcj@Hevake tmp]$ ll test1 test2 -rwxrwxr-x. 1 18784 Mar 19 22:01 test1 -rwxrwxr-x. 1 35184 Mar 19 22:03 test2
Test2는 test1보다 두 배 크기 때문에 그 이유는 말할 필요도 없습니다.
질문 하나 더: 포인터는 유형으로 간주됩니까?
위의 list
그리고 포인터는 무엇이든 동일합니다. void*를 사용하여 모든 포인터 유형을 나타낼 수 있습니다.
그래서 위의 코드를 변경하고 다시 테스트했습니다.
//! code-7 map<int, string*> m1; map<int, string*> m2; map<int, string*> m3; m1.insert(std::make_pair(1, new string("hello"))); m2.insert(std::make_pair(1, new string("hi"))); m3.insert(std::make_pair(1, new string("lichunjun")));
및
//! code-8 map<int, string*> m1; map<int, double*> m2; map<int, int*> m3; m1.insert(std::make_pair(1, new string("hello"))); m2.insert(std::make_pair(1, new double(1.2))); m3.insert(std::make_pair(1, new int(44)));
결과는 다음과 같습니다.
-rwxrwxr-x. 1 18736 Mar 19 23:05 test1 -rwxrwxr-x. 1 35136 Mar 19 23:05 test2
test1과 test2의 예상 결과는 비슷하지만 결과에 따른 최적화가 없어 조금 아쉽네요~
생각: C++이 있을까요? 이를 최적화할 수 있는 매개변수는 무엇입니까?
그렇지 않다면 공간을 절약하기 위해 모든 포인터를 void* 유형으로 정의한 다음 사용할 때 캐스팅하면 됩니다.
//! code-9 map<int, void*> m1; map<int, void*> m2; map<int, void*> m3; m1.insert(std::make_pair(1, new string("hello"))); m2.insert(std::make_pair(1, new double(1.2))); m3.insert(std::make_pair(1, new int(44))); cout << *static_cast<string*>(m1[1]) << endl; cout << *static_cast<double*>(m2[1]) << endl; cout << *static_cast<int*>(m3[1]) << endl;
위 코드는 code-8을 기반으로 하며 모든 사양을 void*로 정의하고 static_cast를 사용하여 사용 시 해당 포인터 유형으로 강제 변환합니다.
code-7과 비교하면 이 방법으로 얻은 코드 크기는 16바이트만 더 많습니다.
그러나 이 접근 방식은 매우 바람직하지 않습니다. void* 포인터를 사용해야 하는 경우 컴파일러는 더 이상 유형을 확인하지 않으며 유형을 혼동하기 쉽습니다.