亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C++ > 正文

C++中的變長參數深入理解

2020-05-23 14:00:00
字體:
來源:轉載
供稿:網友

前言

在吸進的一個項目中為了使用共享內存和自定義內存池,我們自己定義了MemNew函數,且在函數內部對于非pod類型自動執行構造函數。在需要的地方調用自定義的MemNew函數。這樣就帶來一個問題,使用stl的類都有默認構造函數,以及復制構造函數等。但使用共享內存和內存池的類可能沒有默認構造函數,而是定義了多個參數的構造函數,于是如何將參數傳入MemNew函數便成了問題。

一、變長參數函數

首先回顧一下較多使用的變長參數函數,最經典的便是printf

extern int printf(const char *format, ...);

以上是一個變長參數的函數聲明。我們自己定義一個測試函數:

#include <stdarg.h>#include <stdio.h>int testparams(int count, ...){ va_list args; va_start(args, count); for (int i = 0; i < count; ++i) {  int arg = va_arg(args, int);  printf("arg %d = %d", i, arg); } va_end(args); return 0;}int main(){ testparams(3, 10, 11, 12); return 0;}

變長參數函數的解析,使用到三個宏va_start,va_arg va_end,再看va_list的定義 typedef char* va_list; 只是一個char指針。

這幾個宏如何解析傳入的參數呢?

函數的調用,是一個壓棧,保存,跳轉的過程。

簡單的流程描述如下:

      1、把參數從右到左依次壓入棧;

      2、調用call指令,把下一條要執行的指令的地址作為返回地址入棧;(被調用函數執行完后會回到該地址繼續執行)

      3、當前的ebp(基址指針)入棧保存,然后把當前esp(棧頂指針)賦給ebp作為新函數棧幀的基址;

      4、執行被調用函數,局部變量等入棧;

      5、返回值放入eax,leave,ebp賦給esp,esp所存的地址賦給ebp;(這里可能需要拷貝臨時返回對象)
從返回地址開始繼續執行;(把返回地址所存的地址給eip)

由于開始的時候從右至左把參數壓棧,va_start 傳入最左側的參數,往右的參數依次更早被壓入棧,因此地址依次遞增(棧頂地址最?。?code>va_arg傳入當前需要獲得的參數的類型,便可以利用 sizeof 計算偏移量,依次獲取后面的參數值。

#define _INTSIZEOF(n)   ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))#define _ADDRESSOF(v) (&const_cast<char&>(reinterpret_cast<const volatile char&>(v)))#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))#define __crt_va_arg(ap, t)  (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))#define __crt_va_end(ap)  ((void)(ap = (va_list)0))#define __crt_va_start(ap, x) ((void)(__vcrt_va_start_verify_argument_type<decltype(x)>(), __crt_va_start_a(ap, x)))#define va_start __crt_va_start#define va_arg __crt_va_arg#define va_end __crt_va_end

上述宏定義中, _INTSIZEOF(n) 將地址的低2位指令,做內存的4字節對齊。每次取參數時,調用__crt_va_arg(ap,t)  ,返回t類型參數地址的值,同時將ap偏移到t之后。最后,調用_crt_va_end(ap)將ap置0.

變長參數的函數的使用及其原理看了宏定義是很好理解的。從上文可知,要使用變長參數函數的參數,我們必須知道傳入的每個參數的類型。printf中,有format字符串中的特殊字符組合來解析后面的參數類型。但是當傳入類的構造函數的參數時,我們并不知道每個參數都是什么類型,雖然參數能夠依次傳入函數,但無法解析并獲取每個參數的數值。因此傳統的變長參數函數并不足以解決傳入任意構造函數參數的問題。

二、變長參數模板

我們需要用到C++11的新特性,變長參數模板。

這里舉一個使用自定義內存池的例子。定義一個內存池類MemPool.h,以count個類型T為單元分配內存,默認分配一個對象。每當內存內空閑內存不夠,則一次申請MEMPOOL_NEW_SIZE個內存對象。內存池本身只負責內存分配,不做初始化工作,因此不需要傳入任何參數,只需實例化模板分配相應類型的內存即可。

#ifndef UTIL_MEMPOOL_H#define UTIL_MEMPOOL_H#include <stdlib.h>#define MEMPOOL_NEW_SIZE 8template<typename T, size_t count = 1>class MemPool{private: union MemObj {  char _obj[1];  MemObj* _freelink; };public: static void* Allocate() {  if (!_freelist) {   refill();  }  MemObj* alloc_mem = _freelist;  _freelist = _freelist->_freelink;  ++_size;  return (void*)alloc_mem; } static void DeAllocate(void* p) {  MemObj* q = (MemObj*)p;  q->_freelink = _freelist;  _freelist = q;  --_size; } static size_t TotalSize() {  return _totalsize; } static size_t Size() {  return _size; }private: static void refill() {  size_t size = sizeof(T) * count;  char* new_mem = (char*)malloc(size * MEMPOOL_NEW_SIZE);  for (int i = 0; i < MEMPOOL_NEW_SIZE; ++i) {   MemObj* free_mem = (MemObj*)(new_mem + i * size);   free_mem->_freelink = _freelist;   _freelist = free_mem;  }  _totalsize += MEMPOOL_NEW_SIZE; } static MemObj* _freelist; static size_t _totalsize; static size_t _size;};template<typename T, size_t count>typename MemPool<T, count>::MemObj* MemPool<T, count>::_freelist = NULL;template<typename T, size_t count>size_t MemPool<T, count>::_totalsize = 0;template<typename T, size_t count>size_t MemPool<T, count>::_size = 0;#endif

接下來在沒有變長參數的情況下,實現通用MemNewMemDelete函數模板。這里不對函數模板作詳細解釋,用函數模板我們可以對不同的類型實現同樣的內存池分配操作。如下:

template<class T>T *MemNew(size_t count){ T *p = (T*)MemPool<T, count>::Allocate(); if (p != NULL) {  if (!std::is_pod<T>::value)  {   for (size_t i = 0; i < count; ++i)   {    new (&p[i]) T();   }  } } return p;}template<class T>T *MemDelete(T *p, size_t count){ if (p != NULL) {  if (!std::is_pod<T>::value)  {   for (size_t i = 0; i < count; ++i)   {    p[i].~T();   }  }  MemPool<T, count>::DeAllocate(p); }}

上述實現中,使用placement new對申請的內存進行構造,使用了默認構造函數,當申請內存的類型不具備默認構造函數時,placement new將報錯。對于pod類型,可以省去調用構造函數的過程。

引入C++11變長模板參數后MemNew修改為如下

template<class T, class... Args>T *MemNew(size_t count, Args&&... args){ T *p = (T*)MemPool<T, count>::Allocate(); if (p != NULL) {  if (!std::is_pod<T>::value)  {   for (size_t i = 0; i < count; ++i)   {    new (&p[i]) T(std::forward<Args>(args)...);   }  } } return p;}

以上函數定義包含了多個特性,后面我將一一解釋,其中class... Args 表示變長參數模板,函數參數中Args&& 為右值引用。std::forward<Args> 實現參數的完美轉發。這樣,無論傳入的類型具有什么樣的構造函數,都能夠完美執行

C++11中引入了變長參數模板的概念,來解決參數個數不確定的模板。

template<class... T> class Test {};Test<> test0;Test<int> test1;Test<int,int> test2;Test<int,int,long> test3;template<class... T> void test(T... args);test();test<int>(0);test<int,int,long>(0,0,0L);

以上分別是使用變長參數類模板和變長參數函數模板的例子。

2.1變長參數函數模板

T... args 為形參包,其中args是模式,形參包中可以有0到任意多個參數。調用函數時,可以傳任意多個實參。對于函數定義來說,該如何使用參數包呢?在上文的MemNew中,我們使用std::forward依次將參數包傳入構造函數,并不關注每個參數具體是什么。如果需要,我們可以用sizeof...(args)操作獲取參數個數,也可以把參數包展開,對每個參數做更多的事。展開的方法有兩種,遞歸函數,逗號表達式。

遞歸函數方式展開,模板推導的時候,一層層遞歸展開,最后到沒有參數時用定義的一般函數終止。

void test(){}template<class T, class... Args> void test(T first, Args... args){ std::cout << typeid(T).name() << " " << first << std::endl; test(args...);}test<int, int, long>(0, 0, 0L);output:int 0int 0long 0

逗號表達式方式展開,利用數組的參數初始化列表和逗號表達式,逐一執行print每個參數。

template<class T>void print(T arg){ std::cout << typeid(T).name() << " " << arg << std::endl;}template<class... Args>void test(Args... args){ int arr[] = { (print(args), 0)... };}test(0, 0, 0L);output:int 0int 0long 0

2.2變長參數類模板

變長參數類模板,一般情況下可以方便我們做一些編譯期計算??梢酝ㄟ^偏特化和遞歸推導的方式依次展開模板參數。

template<class T, class... Types>class Test{public: enum {  value = Test<T>::value + Test<Types...>::value, };};template<class T>class Test<T>{public: enum {  value = sizeof(T), };};Test<int, int, long> test;std::cout << test.value;output: 12

2.3右值引用和完美轉發

對于變長參數函數模板,需要將形參包展開逐個處理的需求不多,更多的還是像本文的MemNew這樣的需求,最終整個傳入某個現有的函數。我們把重點放在參數的傳遞上。

要理解右值引用,需要先說清楚左值和右值。左值是內存中有確定存儲地址的對象的表達式的值;右值則是非左值的表達式的值。const左值不可被賦值,臨時對象的右值可以被賦值。左值與右值的根本區別在于是否能用&運算符獲得內存地址。

int i =0;//i 左值int *p = &i;// i 左值int& foo();foo() = 42;// foo() 左值int* p1 = &foo();// foo() 左值int foo1();int j = 0;j = foo1();// foo 右值int k = j + 1;// j + 1 右值int *p2 = &foo1(); // 錯誤,無法取右值的地址j = 1;// 1 右值

理解左值和右值之后,再來看引用,對左值的引用就是左值引用,對右值(純右值和臨終值)的引用就是右值引用。

如下函數foo,傳入int類型,返回int類型,這里傳入函數的參數0和返回值0都是右值(不能用&取得地址)。于是,未做優化的情況下,傳入參數0的時候,我們需要把右值0拷貝給param,函數返回的時候需要將0拷貝給臨時對象,臨時對象再拷貝給res。當然現在的編譯器都做了返回值優化,返回對象是直接創建在返回后的左值上的,這里只用來舉個例子

int foo(int param){ printf("%d", param); return 0;}int res = foo(0);

顯然,這里的拷貝都是多余的??赡芪覀儠胍獌灮?,首先將參數int改為int& , 傳入左值引用,于是0無法傳入了,當然我們可以改成const int& ,這樣終于省去了傳參的拷貝。

int foo(const int& param){ printf("%d", param); return 0;}

由于const int& 既可以是左值也可以是右值,傳入0或者int變量都能夠滿足。(但是似乎既然有左值引用的int&類型,就應該有對應的傳入右值引用的類型int&& )。另外,這里返回的右值0,似乎不通過拷貝就無法賦值給左值res 。

于是有了移動語義,把臨時對象的內容直接移動給被賦值的左值對象(std::move)。和右值引用,X&&是到數據類型X的右值引用。

int result = 0;int&& foo(int&& param){ printf("%d", param); return std::move(result);}int&& res = foo(0);int *pres = &res;

foo改為右值引用參數和返回值,返回右值引用,免去拷貝。這里res是具名引用,運算符右側的右值引用作為左值,可以取地址。右值引用既有左值性質,也有右值性質。

上述例子還只存在于拷貝的性能問題?;氐?code>MemNew這樣的函數模板。

template<class T>T* Test(T arg){ return new T(arg);}template<class T>T* Test(T& arg){ return new T(arg);}template<class T>T* Test(const T& arg){ return new T(arg);}template<class T>T* Test(T&& arg){ return new T(std::forward<T>(arg));}

上述的前三種方式傳參,第一種首先有拷貝消耗,其次有的參數就是需要修改的左值。第二種方式則無法傳常數等右值。第三種方式雖然左值右值都能傳,卻無法對傳入的參數進行修改。第四種方式使用右值引用,可以解決參數完美轉發的問題。

  std::forward能夠根據實參的數據類型,返回相應類型的左值和右值引用,將參數完整不動的傳遞下去。

  解釋這個原理涉及到引用塌縮規則

      T& & ->T&

      T& &&->T&

           T&& &->T&

      T&& &&->T&&

template< class T > struct remove_reference  {typedef T type;};template< class T > struct remove_reference<T&> {typedef T type;};template< class T > struct remove_reference<T&&> {typedef T type;};template< class T > T&& forward( typename std::remove_reference<T>::type& t ){ return static_cast<T&&>(t);}template<class T>typename std::remove_reference<T>::type&& move(T&& a) noexcept{  return static_cast<typename std::remove_reference<T>::type&&>(a);}

對于函數模板

template<class T>T* Test(T&& arg){ return new T(std::forward<T>(arg));}

當傳入實參為X類型左值時,T為X&,最后的類型為X&。當實參為X類型右值時,T為X,最后的類型為X&&。

x為左值時:

X x;Test(x);

T為X&,實例化后

 

X& && std::forward(remove_reference<X&>::type& a) noexcept{ return static_cast<X& &&>(a);}X* Test(X& && arg){ return new X(std::forward<X&>(arg)); }// 塌陷后X& std::forward(X& a){ return static_cast<X&>(a);}X* Test(X& arg){ return new X(std::forward<X&>(arg));}

x為右值時:

X foo();Test(foo());

T為X,實例化后

X&& std::forward(remove_reference<X>::type& a) noexcept{ return static_cast<X&&>(a);}X* Test(X&& arg){ return new X(std::forward<X>(arg)); }// 塌陷后X&& std::forward(X& a){ return static_cast<X&&>(a);}X* Test(X&& arg){ return new X(std::forward<X>(arg));}

可以看到最終實參總是被推導為和傳入時相同的類型引用。

至此,我們討論了變長參數模板,討論了右值引用和函數模板的完美轉發,完整的解釋了MemNew對任意多個參數的構造函數的參數傳遞過程。利用變長參數函數模板,右值引用和std::forward ,可以完成參數的完美轉發。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家學習或者使用C++能有所幫助,如果有疑問大家可以留言交流,謝謝大家對VEVB武林網的支持。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩精品日韩在线观看| 日韩免费在线观看视频| 国产精品大陆在线观看| 日韩av高清不卡| 亚洲精品一区二区三区婷婷月| 久久中文字幕视频| 欧美美女18p| 欧美成人免费在线观看| 俺去了亚洲欧美日韩| 久久精品成人欧美大片| 97**国产露脸精品国产| 国产99久久精品一区二区永久免费| 亚洲一区免费网站| 亚洲免费一级电影| 日韩网站免费观看高清| 欧美激情中文字幕在线| 日韩中文在线视频| 欧美国产日韩中文字幕在线| 欧美大片免费观看在线观看网站推荐| 欧美国产日产韩国视频| 91在线免费观看网站| 国产精品伦子伦免费视频| 成人妇女免费播放久久久| 尤物tv国产一区| 欧美成人午夜激情| 亚洲人成电影网站| 俺去了亚洲欧美日韩| 91精品国产自产91精品| 俺去了亚洲欧美日韩| 久久理论片午夜琪琪电影网| 久久久久久成人精品| 国产精品www色诱视频| 青草热久免费精品视频| 欧美性在线观看| 国产一区二区视频在线观看| 97精品视频在线观看| 911国产网站尤物在线观看| 中文字幕日韩免费视频| 欧美综合激情网| 自拍偷拍亚洲精品| 成人午夜黄色影院| 92裸体在线视频网站| 北条麻妃在线一区二区| 91精品国产色综合| 91九色综合久久| 精品亚洲一区二区三区四区五区| 精品久久久久久中文字幕| 日韩欧美在线视频日韩欧美在线视频| 亚洲欧美精品伊人久久| 久久国产精品久久久久久久久久| 91亚洲一区精品| 91wwwcom在线观看| 一区二区三区精品99久久| 91av在线看| 中文字幕日韩有码| 亚洲a级在线播放观看| 岛国av一区二区三区| 日韩欧美福利视频| 久久久噜久噜久久综合| 日韩一区二区三区国产| 亚洲欧洲午夜一线一品| 亚洲第一区在线观看| 欧美成人精品不卡视频在线观看| 日韩精品欧美国产精品忘忧草| 国产精品91一区| 久久国产精品久久国产精品| 最近2019中文字幕大全第二页| 欧美性猛交xxxx久久久| 中文字幕九色91在线| 久久韩剧网电视剧| 国产亚洲欧美日韩一区二区| 精品成人久久av| xxxx欧美18另类的高清| 久久久久国色av免费观看性色| 亚洲欧美日韩网| 2018中文字幕一区二区三区| 欧美黑人xxxx| 一区三区二区视频| 欧美激情亚洲精品| 日韩欧美国产高清91| 亚洲天堂网站在线观看视频| 欧美视频国产精品| 中文字幕亚洲无线码在线一区| 日韩免费在线播放| 亚洲成年人在线| 欧美色视频日本高清在线观看| 98精品国产自产在线观看| 久久久精品久久久久| 精品一区二区三区电影| 亚洲欧美国产高清va在线播| 亚洲人精选亚洲人成在线| 亚洲精品自拍第一页| 亚洲欧美日韩国产精品| 红桃视频成人在线观看| 美女福利精品视频| 欧美日韩激情小视频| 国产日韩欧美综合| 亚洲自拍偷拍视频| 成人网在线免费观看| 日本视频久久久| 国产精品小说在线| 精品久久久久人成| 狠狠躁18三区二区一区| 日韩亚洲精品视频| 日本一区二区三区在线播放| 亚洲激情中文字幕| 日韩最新在线视频| 成人精品视频99在线观看免费| 欧美成人免费观看| 国产精品久久精品| 久久久爽爽爽美女图片| 97婷婷涩涩精品一区| 成人精品一区二区三区| 国内免费久久久久久久久久久| 亚洲精品国产成人| 久久天天躁狠狠躁夜夜躁2014| 免费99精品国产自在在线| 亚洲精品动漫久久久久| 亚洲欧洲日产国产网站| 欧美丰满片xxx777| 亚洲女人被黑人巨大进入| 91色中文字幕| 日韩精品在线观看网站| 黄色一区二区在线| 亚洲图片制服诱惑| 亚洲偷熟乱区亚洲香蕉av| 91精品在线一区| 国产成人亚洲综合91| 久久99视频免费| 91久久久久久久久久| 久久五月天色综合| 亚洲午夜小视频| 欧美成人精品h版在线观看| 茄子视频成人在线| 亚洲毛片在线观看.| 久久影视电视剧免费网站清宫辞电视| 欧美巨乳在线观看| 国产精品91久久久久久| 尤物九九久久国产精品的分类| 91免费看片网站| 亚洲成年人影院在线| 国产精品扒开腿爽爽爽视频| 91社区国产高清| 亚洲欧美变态国产另类| 亚洲网站在线观看| 不卡中文字幕av| 91久久久久久久久久久| 欧美黑人xxxx| 在线午夜精品自拍| 欧美视频第一页| 久久免费视频网站| 久久99久久久久久久噜噜| 一本色道久久综合狠狠躁篇怎么玩| 亚洲免费一在线| 日韩在线中文视频| 国产精品久久久亚洲| 91美女片黄在线观| 国产一区香蕉久久| 久久精品中文字幕免费mv| 色婷婷久久一区二区| 亚洲第一精品自拍| 亚洲成人黄色在线观看| 日韩电影中文字幕在线| 久久久久久久国产|