C++使用泛型导致的膨胀问题

目录

临峰不畏博主从事C++软件开发多年,由于之前的开发环境都是资源充足的服务器,不用考虑磁盘空间的问题。最近打算在智能家居主机的嵌入式平台上使用C++进行开发。FLASH存储空间有限,这是必须要考虑的因素,一定要重视。

如下定义两个list,元素类型不同:

list<int> l1;
list<string> l2;

如果是用C语来做应该怎么办?它会对应list<int>写一套代码,再对list<string>写一套。每套都有相同的成员函数,只是变量类型各自不同罢了。

下面是list<int>的C语言实现方式:

//! code-1
struct 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<string>的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<double>的:

//! code-4

LIST_DECLARE(double)

所以,泛型产生冗余代码是无法避免的,至少用C来做这样的泛型也是无法避免的。
既然无法避免的,那就看看怎么尽可能以避免上述的问题。在《Effective C++》中有一章节专门提到:不要在模板中使用不必要的参数。因为每一个不同的参数编译器都会为之生成一套相应的代码。
如果代码中只有一种数据类型,就算用该类型定义了多个变量,编译器是不是只会生成一套相关的代码?(应该是这样的)。

写个例子对比一下:(省略不必要的代码)

test1.cpp ,里面只有map<int, string>,但定义了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

test2test1大一倍,原因不用多说。

还有一个问题:指针是不是被认为是一个类型?
上面的list<int>list<string>不能共用同一套代码,根据的原因是因为intstring这两种类型在空间大小与赋值的方式上都是不同的。所以,必须生成两套代码来实现。
而指针,不管是什么指针,它们都是一样的。我们可以用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

预期的结果test1test2相差不多,但从结果上看并没有什么优化,结果有点令人失望~

思考: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++使用泛型导致的膨胀问题的文章就介绍到这了,更多相关C++使用泛型导致的膨胀问题内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 自定义的Troop<T>泛型类( c++, java和c#)的实现代码

    Troop<T>是一个泛型列表操作类,适用于非高性能和非大数据量的要求.包括了:取值get,赋值set,追加append,插入insert,清除remove,进队enqueue,出队dequeue,交换swap,滚动roll,进栈push,出栈pop等日常操作. //for more information, please access http://www.one-lab.net using System; using System.Collections.Generic; using Sy

  • C++实现支持泛型的LFU详解

    首先定义LFU存储数据节点ListNode的结构, 此结构支持键K和值V的模板,为了在有序元素中实现比较(严格小于),这里需要重载小于号,如果此数据的使用频次最少,则小于结果为true,如果频次相等,轮次早的数据最小. template<typename K, typename V> struct ListNode { K key; V value; int freq; long cur; bool operator<(const ListNode &x) const { if

  • C++ 泛型编程详解

    泛型编程与面向对象编程的目标相同,即使重用代码和抽象通用概念的技术更加简单.但是面向对象编程强调编程的数据方面,泛型编程强调的是独立于特定数据类型. 这一篇介绍一下 C++ 编程中与面向对象并列的另一大分支--泛型编程,这一篇主要介绍函数模板.类模板和成员模板三大部分 如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢 泛型编程 模板是泛型编程的一种重要思想,STL(Standard Template Library,标准模板库)是采用模板实现的一个实例 函数模板 对比函数重载(同一作用域内函数

  • 关于C++ TpeScript系列的泛型

    目录 一.模版 二.泛型 三.泛型递归 四.默认泛型参数 五.泛型重载 前言: 我在面试的时候,通常喜欢问候选人一些莫名其妙的问题.比如这样的问题,假如你是某个库的作者,你如何实现某个功能.这类问题一般没有正确的答案,主要意图是考察一下候选人对这个库有没有更深入的理解,次要意图是觉得这样挺好玩.玩归玩,但该严肃的时候也要严肃起来.有一次,我面试到一位用过TypeScript的同学,这让人眼前一亮(从我的经验看,国内偶尔有大厂会用,小厂基本没有).随后,我问了句,你是怎么理解泛型的呢?问了之后,我

  • C++泛型算法的一些总结

    泛型算法的一些总结1.每个泛型算法的实现都独立于单独的容器,并且不依赖于容器存储的元素类型. 2.泛型算法从不直接添加或删除元素. 3.与容器的类型无关,只在一点上隐式地依赖元素类型:必须能够对元素做比较运算. A.需要某种遍历集合的方式:能够从一个元素向前移到下一个元素. B.必须能够知道是否到达了集合的末尾. C.必须能够对容器中的每一个元素与被查找的元素进行比较. D.需要一个类型来指示元素在容器中的位置,或者表示找不到该元素. 4.迭代器将算法和容器绑定起来.算法基于迭代器及其操作实现,

  • C++泛型编程基本概念详解

    目录 1.什么是泛型编程? 2.函数模板 (1)函数模板概念 (2)函数模板格式 (3)函数模板的原理 (4)函数模板的实例化 (5)模板参数的匹配原则 3.类模板 (1)类模板的定义格式 (2)类模板的实例化 总结 1.什么是泛型编程? 比如说,我们如何实现一个通用的交换函数呢?int型.double型.char型的交换 void Swap(int& left, int& right) { int temp = left; left = right; right = temp; } vo

  • C++实现的泛型List类分享

    额,不要说我三心二意:一边在看.NET和CLR的原理.一边在看JavaScript.一边在看Java:有时看算法有时看Unity.Hibernate:有时看Hadoop有时看Redis:现在又开始看C++了. 以前觉得无论什么语言嘛,其实都差不多,核心思想基本一致.现在又不这么想了,其实语言的选择对软件的性能.可靠性.开发成本之类的关系很大,所以觉得还是要多接触一些比较核心的东西--那么自然是C++了.以前在学校学的C++完全是酱油,太水了基本没啥用,用来用去和C差不多,所以现在要自己学啦. 废

  • C++算法与泛型算法(algorithm、numeric)

    本文包括的算法有: 只读算法:find().count().accumulate().equal() 写算法:fill().fill_n().back_inserter().copy().copy_backward().replace().replace_copy().next_permutation().prev_permutation() 重排元素算法:sort().stable_sort().unique() 一.算法简介 大多数算法在头文件algorithm中.标准库还在头文件numer

  • C++使用泛型导致的膨胀问题

    目录 临峰不畏博主从事C++软件开发多年,由于之前的开发环境都是资源充足的服务器,不用考虑磁盘空间的问题.最近打算在智能家居主机的嵌入式平台上使用C++进行开发.FLASH存储空间有限,这是必须要考虑的因素,一定要重视. 如下定义两个list,元素类型不同: list<int> l1; list<string> l2; 如果是用C语来做应该怎么办?它会对应list<int>写一套代码,再对list<string>写一套.每套都有相同的成员函数,只是变量类型各

  • C++里最容易忽视却不能忽视的问题(必看)

    1 define 只是简单地文本替换. 2 每个机器的字长不同. 3 每个类型在不同的机器上,所占用的内存空间不同. 4 每个机器内部的字节大小端不同. 5 并不是所有的编译器或机器都支持最新的C++标准. 6 并非所有的编译器都是从右往左执行单行的多个表达式. 7 返回struct时,各个机器有不同的优化手段.因此最好使用指针或引用. 8 并非所有的运算符都可以重载,并非""不能重载. 9 并非所有的编译器都支持'\uxxx'的Unicode. 10 并非所有的默认值都为0. 11

  • 聊聊Java并发中的Synchronized

    1 引言 在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,本文详细介绍了Java SE1.6中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级锁,以及锁的存储结构和升级过程. 2 术语定义 术语 英文 说明 CAS Compare and Swap 比较并设置.用于在硬件层面上提供原子性操作.在 Intel 处理器中,比较并交换通过指令cmpxch

  • 浅谈JavaScript 代码整洁之道

    概述 一张幽默的图片:软件质量通过你在阅读代码的时候有多少报怨来进行评估 Robert C. Martin 在 <代码整洁之道> 中提到的软件工程原则,同样适用于 JavaScript.这不是一个风格参考.它指导如何用 JavaScript 编写可读.可复用.可重构的软件. 并不是每一个原则都必须严格遵循,甚至很少得到大家的认同.它们仅用于参考,不过要知道这些原则都是<代码整洁之道>的作者们累积多年的集体经验. 我们在软件工程方面的技术发展刚刚超过 50 年,我们仍然在学习很多东西

  • 建立在Tablestore的Wifi设备监管系统架构实现

    Wifi设备监管 公司通过监管系统维护Wifi设备属性.采集Wifi设备监控数据.当需要Wifi设备上.下线时,通过监管系统操作完成设备的添加.下线,同时可通过系统修改.增加设备属性信息,如:设备mac地址.设备型号.设备地理位置等.设备上线后,会定期向系统推送监控数据,从而完成设备监控数据的采集.采集数据包含:cpu.内存.连接数.Wan口流量与流速.2.4G与5G模块的信道数据等. 通过分析监控数据指标.分析设备运行状态,动态将问题设备的运行状态修改为:预警.报警.借助系统,网络部门可以快速

  • C++初阶学习之模板进阶

    目录 一.非模板类型参数 二.模板特化 1.函数模板特化 2.类模板特化 1)全特化 2)偏特化 三.模板分离编译 四.模板总结 总结 一.非模板类型参数 分类: 模板参数分类类型形参与非类型形参 概念: 1.类型形参: 出现在模板参数列表中,跟在class或者typename之类的参数类型名称 2.非类型形参: 用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用 示例: namespace cole { // 定义一个模板类型的静态数组 template<cla

  • C++ 深入浅出探索模板

    目录 非类型模板参数 模板特化 函数模板特化 类模板特化 全特化 偏特化 模板分离编译 模板的分离编译 解决方法 总结 非类型模板参数 模板参数分类类型形参与非类型形参. 类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称. 非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用. 注意: 浮点数,类对象以及字符串是不允许作为非类型模板的. 非类型的模板参数必须在编译期就能确认结果. 模板特化 有时候,编译默认函数模板

  • C++模板Template详解及其作用介绍

    目录 1. 模板 2. 函数模板 2.1 函数模板概念 2.2 函数模板格式 2.3 函数模板原理 2.4 函数模板的实例化 2.5 模板参数的匹配原则 2.6声明定义分离 3. 类模板 3.1 类模板格式 3.2 类模板的实例化 3.3 类模板中函数放在类外进行定义时 4. 模板分离编译 4.1 什么是分离编译 4.2 模板的分离编译 5. 缺省值与返回值 6. 总结 1. 模板 首先模板分为函数模板和类模板 想到模板,就会联想到泛型编程 泛型编程:编写与类型无关的通用代码,是代码复用的一种手

  • Android架构发展进化详解

    目录 一.MVC架构 1.概述 2.例子 二.MVP架构 1.概述 2.例子 三.MVVM架构 1.概述 2.例子 四.Clean架构 1.概述 2.例子 五.MVI架构 1.概述 2.例子 六.总结 1.从MVC架构到MVI架构 2.从clean code到clean coder 3.MVI架构之后 一.MVC架构 1.概述 MVC架构是第一个应用于Android开发的成熟架构,由Model.View.Controller三部分组成: Model:负责数据的存储及相关逻辑. View:负责界面

  • PostgreSQL长事务概念解析

    我们在很多地方应该都听到过长事务的危害,比方说长事务会导致表膨胀之类的.那么在PostgreSQL中什么才算是长事务呢? 首先,在PostgreSQL的官方文档中并没有所谓“长事务”这一定义,似乎大家约定俗称的把一个执行了很长却没有提交的事务认为是“长事务”了,而在不同的数据库中关于长事务的定义往往也不尽相同,那么在PostgreSQL中什么是长事务呢? 打个比方,如下所示,我在一个会话中通过begin开启一个事务,然后执行了个简单的查询语句后迟迟不提交,这算不算长事务呢? bill=# beg

随机推荐