C++에서 제네릭 사용으로 인해 발생하는 확장 문제

高洛峰
풀어 주다: 2016-11-22 17:33:41
원래의
1352명이 탐색했습니다.

며칠 전, 한 블로거는 생성된 실행 파일에서 코드가 너무 커진다는 이유로 C++의 제네릭을 비판하는 기사를 읽었습니다.

이 블로거는 수년간 C++ 소프트웨어 개발에 종사해 왔으며 이전 개발 환경은 리소스가 충분한 서버였기 때문에 디스크 공간 문제를 고려할 필요가 없었습니다. 최근에는 스마트홈 호스트의 임베디드 플랫폼 개발에 C++를 사용할 계획입니다. FLASH 저장 공간은 제한되어 있으므로 반드시 고려하고 주의해야 하는 요소입니다.

다음과 같이 요소 유형이 서로 다른 두 개의 목록을 정의합니다.

list<int> l1;
list<string> l2;
로그인 후 복사

C 언어를 사용하는 경우 어떻게 해야 합니까? 이는 list에 해당하는 코드 세트 하나와 list에 대한 또 다른 코드 세트를 작성합니다. 각 세트에는 동일한 멤버 함수가 있고 변수 유형만 다릅니다.

다음은 list의 C 언어 구현입니다.

//! 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의 C 언어 구현입니다.

//! 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만 있고 m1, m2, m3이 정의되어 있습니다.

//! 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와 list은 동일한 코드 집합을 공유할 수 없습니다. 그 이유는 int와 string의 두 유형이 공간 크기와 할당 방법이 다르기 때문입니다. 따라서 이를 달성하려면 두 세트의 코드를 생성해야 합니다.

그리고 포인터는 무엇이든 동일합니다. 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* 포인터를 사용해야 하는 경우 컴파일러는 더 이상 유형을 확인하지 않으며 유형을 혼동하기 쉽습니다.


관련 라벨:
c++
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!