自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

C++語言的15個晦澀特性

開發(fā) 后端
這個列表收集了 C++ 語言的一些晦澀(Obscure)特性,是我經(jīng)年累月研究這門語言的各個方面收集起來的。C++非常龐大,我總是能學(xué)到一些新知識。即使你對C++已了如指掌,也希望你能從列表中學(xué)到一些東西。下面列舉的特性,根據(jù)晦澀程度由淺入深進(jìn)行排序。

這個列表收集了 C++ 語言的一些晦澀(Obscure)特性,是我經(jīng)年累月研究這門語言的各個方面收集起來的。C++非常龐大,我總是能學(xué)到一些新知識。即使你對C++已了如指掌,也希望你能從列表中學(xué)到一些東西。下面列舉的特性,根據(jù)晦澀程度由淺入深進(jìn)行排序。

  • 1. 方括號的真正含義
  • 2. 最煩人的解析
  • 3.替代運(yùn)算標(biāo)記符
  • 4. 重定義關(guān)鍵字
  • 5. Placement new
  • 6.在聲明變量的同時進(jìn)行分支
  • 7.成員函數(shù)的引用修飾符
  • 8.轉(zhuǎn)向完整的模板元編程
  • 9.指向成員的指針操作符
  • 10. 靜態(tài)實(shí)例方法
  • 11.重載++和–
  • 12.操作符重載和檢查順序
  • 13.函數(shù)作為模板參數(shù)
  • 14.模板的參數(shù)也是模板
  • 15.try塊作為函數(shù)

方括號的真正含義

用來訪問數(shù)組元素的ptr[3]其實(shí)只是*(ptr + 3)的縮寫,與用*(3 + ptr)是等價的,因此反過來與3[ptr]也是等價的,使用3[ptr]是完全有效的代碼

最煩人的解析

“most vexing parse”這個詞是由Scott Meyers提出來的,因?yàn)镃++語法聲明的二義性會導(dǎo)致有悖常理的行為:

  1. // 這個解釋正確? 
  2. // 1) 類型std::string的變量會通過std::string()實(shí)例化嗎? 
  3. // 2) 一個函數(shù)聲明,返回一個std::string值并有一個函數(shù)指針參數(shù), 
  4. // 該函數(shù)也返回一個std::string但沒有參數(shù)? 
  5. std::string foo(std::string()); 
  6.   
  7. // 還是這個正確? 
  8. // 1)類型int變量會通過int(x)實(shí)例化嗎? 
  9. // 2)一個函數(shù)聲明,返回一個int值并有一個參數(shù), 
  10. // 該參數(shù)是一個名為x的int型變量嗎? 
  11. int bar(int(x)); 

兩種情形下C++標(biāo)準(zhǔn)要求的是第二種解釋,即使***種解釋看起來更直觀。程序員可以通過包圍括號中變量的初始值來消除歧義:

  1. //加括號消除歧義 
  2. std::string foo((std::string())); 
  3. int bar((int(x))); 

第二種情形讓人產(chǎn)生二義性的原因是int y = 3;等價于int(y) = 3;

譯者注:這一點(diǎn)我覺得有點(diǎn)迷惑,下面是我在g++下的測試用例:

  1. #include <iostream> 
  2. #include <string> 
  3. using namespace std; 
  4.   
  5. int bar(int(x));   // 等價于int bar(int x) 
  6.   
  7. string foo(string());  // 等價于string foo(string (*)()) 
  8.   
  9. string test() { 
  10.     return "test"
  11.   
  12. int main() 
  13.     cout << bar(2) << endl; // 輸出2 
  14.     cout << foo(test); // 輸出test 
  15.     return 0; 
  16.   
  17. int bar(int(x)) {  
  18.     return x; 
  19.   
  20. string foo(string (*fun)()) { 
  21.     return (*fun)(); 

能正確輸出,但如果按作者意思添加上括號后再編譯就會報一堆錯誤:“在此作用域尚未聲明”、“重定義”等,還不清楚作者的意圖。

替代運(yùn)算標(biāo)記符

標(biāo)記符and, and_eq, bitand, bitor, compl, not, not_eq, or, or_eq, xor, xor_eq, <%, %>, <: 和 :>都可以用來代替我們常用的&&, &=, &, |, ~, !, !=, ||, |=, ^, ^=, {, }, [ 和 ]。在鍵盤上缺乏必要的符號時你可以使用這些運(yùn)算標(biāo)記符來代替。

重定義關(guān)鍵字

通過預(yù)處理器重定義關(guān)鍵字從技術(shù)上講會引起錯誤,但實(shí)際上是允許這樣做的。因此你可以使用類似#define true false 或 #define else來搞點(diǎn)惡作劇。但是,也有它合法有用的時候,例如,如果你正在使用一個很大的庫而且需要繞過C++訪問保護(hù)機(jī)制,除了給庫打補(bǔ)丁的方法外,你也可 以在包含該庫頭文件之前關(guān)閉訪問保護(hù)來解決,但要記得在包含庫頭文件之后一定要打開保護(hù)機(jī)制!

  1. #define class struct 
  2. #define private public 
  3. #define protected public 
  4.   
  5. #include "library.h" 
  6.   
  7. #undef class 
  8. #undef private 
  9. #undef protected 

注意這種方式不是每一次都有效,跟你的編譯器有關(guān)。當(dāng)實(shí)例變量沒有被訪問控制符修飾時,C++只需要將這些實(shí)例變量順序布局即可,所以編譯器可以對 訪問控制符組重新排序來自由更改內(nèi)存布局。例如,允許編譯器移動所有的私有成員放到公有成員的后面。另一個潛在的問題是名稱重整(name mangling),Microsoft的C++編譯器將訪問控制符合并到它們的name mangling表里,因此改變訪問控制符意味著將破壞現(xiàn)有編譯代碼的兼容性。

譯者注:在C++中,Name Mangling 是為了支持重載而加入的一項(xiàng)技術(shù)。編譯器將目標(biāo)源文件中的名字進(jìn)行調(diào)整,這樣在目標(biāo)文件符號表中和連接過程中使用的名字和編譯目標(biāo)文件的源程序中的名字不一樣,從而實(shí)現(xiàn)重載。

#p#

Placement new

Placement new是new操作符的一個替代語法,作用在已分配的對象上,該對象已有正確的大小和正確的賦值,這包括建立虛函數(shù)表和調(diào)用構(gòu)造函數(shù)。

譯者注:placement new就是在用戶指定的內(nèi)存位置上構(gòu)建新的對象,這個構(gòu)建過程不需要額外分配內(nèi)存,只需要調(diào)用對象的構(gòu)造函數(shù)即可。placement new實(shí)際上是把原本new做的兩步工作分開來:***步自己分配內(nèi)存,第二步調(diào)用類的構(gòu)造函數(shù)在自己已分配的內(nèi)存上構(gòu)建新的對象。placement new的好處:1)在已分配好的內(nèi)存上進(jìn)行對象的構(gòu)建,構(gòu)建速度快。2)已分配好的內(nèi)存可以反復(fù)利用,有效的避免內(nèi)存碎片問題。

  1. #include <iostream> 
  2. using namespace std; 
  3.   
  4. struct Test { 
  5.   int data; 
  6.   Test() { cout << "Test::Test()" << endl; } 
  7.   ~Test() { cout << "Test::~Test()" << endl; } 
  8. }; 
  9.   
  10. int main() { 
  11.   // Must allocate our own memory 
  12.   Test *ptr = (Test *)malloc(sizeof(Test)); 
  13.   
  14.   // Use placement new 
  15.   new (ptr) Test; 
  16.   
  17.   // Must call the destructor ourselves 
  18.   ptr->~Test(); 
  19.   
  20.   // Must release the memory ourselves 
  21.   free(ptr); 
  22.   
  23.   return 0; 

當(dāng)在性能關(guān)鍵的場合需要自定義分配器時可以使用Placement new。例如,一個slab分配器從單個的大內(nèi)存塊開始,使用placement new在塊里順序分配對象。這不僅避免了內(nèi)存碎片,也節(jié)省了malloc引起的堆遍歷的開銷。

在聲明變量的同時進(jìn)行分支

C++包含一個語法縮寫,能在聲明變量的同時進(jìn)行分支??雌饋砑认駟蝹€的變量聲明也可以有if或while這樣的分支條件。

  1. struct Event { virtual ~Event() {} }; 
  2. struct MouseEvent : Event { int x, y; }; 
  3. struct KeyboardEvent : Event { int key; }; 
  4.   
  5. void log(Event *event) { 
  6.   if (MouseEvent *mouse = dynamic_cast<MouseEvent *>(event)) 
  7.     std::cout << "MouseEvent " << mouse->x << " " << mouse->y << std::endl; 
  8.   
  9.   else if (KeyboardEvent *keyboard = dynamic_cast<KeyboardEvent *>(event)) 
  10.     std::cout << "KeyboardEvent " << keyboard->key << std::endl; 
  11.   
  12.   else 
  13.     std::cout << "Event" << std::endl; 

成員函數(shù)的引用修飾符

C++11允許成員函數(shù)在對象的值類型上進(jìn)行重載,this指針會將該對象作為一個引用修飾符。引用修飾符會放在cv限定詞(譯者注:CV限定詞有 三種:const限定符、volatile限定符和const-volatile限定符)相同的位置并依據(jù)this對象是左值還是右值影響重載解析:

  1. #include <iostream> 
  2.   
  3. struct Foo { 
  4.   void foo() & { std::cout << "lvalue" << std::endl; } 
  5.   void foo() && { std::cout << "rvalue" << std::endl; } 
  6. }; 
  7.   
  8. int main() { 
  9.   Foo foo; 
  10.   foo.foo(); // Prints "lvalue" 
  11.   Foo().foo(); // Prints "rvalue" 
  12.   return 0; 

轉(zhuǎn)向完整的模板元編程

C++模板是為了實(shí)現(xiàn)編譯時元編程,也就是該程序能生成其它的程序。設(shè)計(jì)模板系統(tǒng)的初衷是進(jìn)行簡單的類型替換,但是在C++標(biāo)準(zhǔn)化過程中突然發(fā)現(xiàn)模板實(shí)際上功能十分強(qiáng)大,足以執(zhí)行任意計(jì)算,雖然很笨拙很低效,但通過模板特化的確可以完成一些計(jì)算:

  1. // Recursive template for general case 
  2. template <int N> 
  3. struct factorial { 
  4.   enum { value = N * factorial<N - 1>::value }; 
  5. }; 
  6.   
  7. // Template specialization for base case 
  8. template <> 
  9. struct factorial<0> { 
  10.   enum { value = 1 }; 
  11. }; 
  12.   
  13. enum { result = factorial<5>::value }; // 5 * 4 * 3 * 2 * 1 == 120 

C++模板可以被認(rèn)為是一種功能型編程語言,因?yàn)樗鼈兪褂眠f歸而非迭代而且包含不可變狀態(tài)。你可以使用typedef創(chuàng)建一個任意類型的變量,使用enum創(chuàng)建一個int型變量,數(shù)據(jù)結(jié)構(gòu)內(nèi)嵌在類型自身。

  1. // Compile-time list of integers 
  2. template <int D, typename N> 
  3. struct node { 
  4.   enum { data = D }; 
  5.   typedef N next; 
  6. }; 
  7. struct end {}; 
  8.   
  9. // Compile-time sum function 
  10. template <typename L> 
  11. struct sum { 
  12.   enum { value = L::data + sum<typename L::next>::value }; 
  13. }; 
  14. template <> 
  15. struct sum<end> { 
  16.   enum { value = 0 }; 
  17. }; 
  18.   
  19. // Data structures are embedded in types 
  20. typedef node<1, node<2, node<3, end> > > list123; 
  21. enum { total = sum<list123>::value }; // 1 + 2 + 3 == 6 

當(dāng)然這些例子沒什么用,但模板元編程的確可以做一些有用的事情,比如可以操作類型列表。但是,使用C++模板的編程語言可用性極低,因此請謹(jǐn)慎和少量使用。模板代碼很難閱讀,編譯速度慢,而且因其冗長和迷惑的錯誤信息而難以調(diào)試。

#p#

指向成員的指針操作符

指向成員的指針操作符可以讓你在一個類的任何實(shí)例上描述指向某個成員的指針。有兩種pointer-to-member操作符,取值操作符*和指針操作符->:

  1. #include <iostream> 
  2. using namespace std; 
  3.   
  4. struct Test { 
  5.   int num; 
  6.   void func() {} 
  7. }; 
  8.   
  9. // Notice the extra "Test::" in the pointer type 
  10. int Test::*ptr_num = &Test::num; 
  11. void (Test::*ptr_func)() = &Test::func; 
  12.   
  13. int main() { 
  14.   Test t; 
  15.   Test *pt = new Test; 
  16.   
  17.   // Call the stored member function 
  18.   (t.*ptr_func)(); 
  19.   (pt->*ptr_func)(); 
  20.   
  21.   // Set the variable in the stored member slot 
  22.   t.*ptr_num = 1; 
  23.   pt->*ptr_num = 2; 
  24.   
  25.   delete pt; 
  26.   return 0; 

該特征實(shí)際上十分有用,尤其在寫庫的時候。例如,Boost::Python, 一個用來將C++綁定到Python對象的庫,就使用成員指針操作符,在包裝對象時很容易的指向成員。

  1. #include <iostream> 
  2. #include <boost/python.hpp> 
  3. using namespace boost::python; 
  4.   
  5. struct World { 
  6.   std::string msg; 
  7.   void greet() { std::cout << msg << std::endl; } 
  8. }; 
  9.   
  10. BOOST_PYTHON_MODULE(hello) { 
  11.   class_<World>("World"
  12.     .def_readwrite("msg", &World::msg) 
  13.     .def("greet", &World::greet); 

記住使用成員函數(shù)指針與普通函數(shù)指針是不同的。在成員函數(shù)指針和普通函數(shù)指針之間casting是無效的。例如,Microsoft編譯器里的成員 函數(shù)使用了一個稱為thiscall的優(yōu)化調(diào)用約定,thiscall將this參數(shù)放到ecx寄存器里,而普通函數(shù)的調(diào)用約定卻是在棧上解析所有的參 數(shù)。

而且,成員函數(shù)指針可能比普通指針大四倍左右,編譯器需要存儲函數(shù)體的地址,到正確父地址(多個繼承)的偏移,虛函數(shù)表(虛繼承)中另一個偏移的索引,甚至在對象自身內(nèi)部的虛函數(shù)表的偏移也需要存儲(為了前向聲明類型)。

  1. #include <iostream> 
  2.   
  3. struct A {}; 
  4. struct B : virtual A {}; 
  5. struct C {}; 
  6. struct D : A, C {}; 
  7. struct E; 
  8.   
  9. int main() { 
  10.   std::cout << sizeof(void (A::*)()) << std::endl; 
  11.   std::cout << sizeof(void (B::*)()) << std::endl; 
  12.   std::cout << sizeof(void (D::*)()) << std::endl; 
  13.   std::cout << sizeof(void (E::*)()) << std::endl; 
  14.   return 0; 
  15.   
  16. // 32-bit Visual C++ 2008:  A = 4, B = 8, D = 12, E = 16 
  17. // 32-bit GCC 4.2.1:        A = 8, B = 8, D = 8,  E = 8 
  18. // 32-bit Digital Mars C++: A = 4, B = 4, D = 4,  E = 4 

在Digital Mars編譯器里所有的成員函數(shù)都是相同的大小,這是源于這樣一個聰明的設(shè)計(jì):生成“thunk”函數(shù)來運(yùn)用右偏移而不是存儲指針自身內(nèi)部的偏移。

靜態(tài)實(shí)例方法

C++中可以通過實(shí)例調(diào)用靜態(tài)方法也可以通過類直接調(diào)用。這可以使你不需要更新任何調(diào)用點(diǎn)就可以將實(shí)例方法修改為靜態(tài)方法。

  1. struct Foo { 
  2.   static void foo() {} 
  3. }; 
  4.   
  5. // These are equivalent 
  6. Foo::foo(); 
  7. Foo().foo(); 

重載++和–

C++的設(shè)計(jì)中自定義操作符的函數(shù)名稱就是操作符本身,這在大部分情況下都工作的很好。例如,一元操作符的-和二元操作符的-(取反和相減)可以通 過參數(shù)個數(shù)來區(qū)分。但這對于一元遞增和遞減操作符卻不奏效,因?yàn)樗鼈兊奶卣魉坪跬耆嗤++語言有一個很笨拙的技巧來解決這個問題:后綴++和–操作 符必須有一個空的int參數(shù)作為標(biāo)記讓編譯器知道要進(jìn)行后綴操作(是的,只有int類型有效)。

  1. struct Number { 
  2.   Number &operator ++ (); // Generate a prefix ++ operator 
  3.   Number operator ++ (int); // Generate a postfix ++ operator 
  4. }; 

操作符重載和檢查順序

重載,(逗號),||或者&&操作符會引起混亂,因?yàn)樗蚱屏苏5臋z查規(guī)則。通常情況下,逗號操作符在整個左邊檢查完畢才開始檢 查右邊,|| 和 &&操作符有短路行為:僅在必要時才會去檢查右邊。無論如何,操作符的重載版本僅僅是函數(shù)調(diào)用且函數(shù)調(diào)用以未指定的順序檢查它們的參數(shù)。

重載這些操作符只是一種濫用C++語法的方式。作為一個實(shí)例,下面我給出一個Python形式的無括號版打印語句的C++實(shí)現(xiàn):

  1. #include <iostream> 
  2.   
  3. namespace __hidden__ { 
  4.   struct print { 
  5.     bool space; 
  6.     print() : space(false) {} 
  7.     ~print() { std::cout << std::endl; } 
  8.   
  9.     template <typename T> 
  10.     print &operator , (const T &t) { 
  11.       if (space) std::cout << ' '
  12.       else space = true
  13.       std::cout << t; 
  14.       return *this
  15.     } 
  16.   }; 
  17.   
  18. #define print __hidden__::print(), 
  19.   
  20. int main() { 
  21.   int a = 1, b = 2; 
  22.   print "this is a test"
  23.   print "the sum of", a, "and", b, "is", a + b; 
  24.   return 0; 

#p#

函數(shù)作為模板參數(shù)

眾所周知,模板參數(shù)可以是特定的整數(shù)也可以是特定的函數(shù)。這使得編譯器在實(shí)例化模板代碼時內(nèi)聯(lián)調(diào)用特定的函數(shù)以獲得更高效的執(zhí)行。下面的例子里,函數(shù)memoize的模板參數(shù)也是一個函數(shù)且只有新的參數(shù)值才通過函數(shù)調(diào)用(舊的參數(shù)值可以通過cache獲得):

  1. #include <map> 
  2.   
  3. template <int (*f)(int)> 
  4. int memoize(int x) { 
  5.   static std::map<intint> cache; 
  6.   std::map<intint>::iterator y = cache.find(x); 
  7.   if (y != cache.end()) return y->second; 
  8.   return cache[x] = f(x); 
  9.   
  10. int fib(int n) { 
  11.   if (n < 2) return n; 
  12.   return memoize<fib>(n - 1) + memoize<fib>(n - 2); 

模板的參數(shù)也是模板

模板參數(shù)實(shí)際上自身的參數(shù)也可以是模板,這可以讓你在實(shí)例化一個模板時可以不用模板參數(shù)就能夠傳遞模板類型??聪旅娴拇a:

  1. template <typename T> 
  2. struct Cache { ... }; 
  3.   
  4. template <typename T> 
  5. struct NetworkStore { ... }; 
  6.   
  7. template <typename T> 
  8. struct MemoryStore { ... }; 
  9.   
  10. template <typename Store, typename T> 
  11. struct CachedStore { 
  12.   Store store; 
  13.   Cache<T> cache; 
  14. }; 
  15.   
  16. CachedStore<NetworkStore<int>, int> a; 
  17. CachedStore<MemoryStore<int>, int> b; 

CachedStore的cache存儲的數(shù)據(jù)類型與store的類型相同。然而我們在實(shí)例化一個CachedStore必須重復(fù)寫數(shù)據(jù)類型(上面的代碼 是int型),store本身要寫,CachedStore也要寫,關(guān)鍵是我們這并不能保證兩者的數(shù)據(jù)類型是一致的。我們真的只想要確定數(shù)據(jù)類型一次即 可,所以我們可以強(qiáng)制其不變,但是沒有類型參數(shù)的列表會引起編譯出錯:

  1. // 下面編譯通不過,因?yàn)镹etworkStore和MemoryStore缺失類型參數(shù) 
  2. CachedStore<NetworkStore, int> c; 
  3. CachedStore<MemoryStore, int> d; 

模板的模板參數(shù)可以讓我們獲得想要的語法。注意你必須使用class關(guān)鍵字作為模板參數(shù)(他們自身的參數(shù)也是模板)

  1. template <template <typenameclass Store, typename T> 
  2. struct CachedStore2 { 
  3.   Store<T> store; 
  4.   Cache<T> cache; 
  5. }; 
  6.   
  7. CachedStore2<NetworkStore, int> e; 
  8. CachedStore2<MemoryStore, int> f; 

try塊作為函數(shù)

函數(shù)的try塊會在檢查構(gòu)造函數(shù)的初始化列表時捕獲拋出的異常。你不能在初始化列表的周圍加上try-catch塊,因?yàn)槠渲荒艹霈F(xiàn)在函數(shù)體外。為了解決這個問題,C++允許try-catch塊也可作為函數(shù)體:

  1. int f() { throw 0; } 
  2.   
  3. // 這里沒有辦法捕獲由f()拋出的異常 
  4. struct A { 
  5.   int a; 
  6.   A::A() : a(f()) {} 
  7. }; 
  8.   
  9. // 如果try-catch塊被用作函數(shù)體并且初始化列表移至try關(guān)鍵字之后的話, 
  10. // 那么由f()拋出的異常就可以捕獲到 
  11. struct B { 
  12.   int b; 
  13.   B::B() try : b(f()) { 
  14.   } catch(int e) { 
  15.   } 
  16. }; 

奇怪的是,這種語法不僅僅局限于構(gòu)造函數(shù),也可用于其他的所有函數(shù)定義。

原文鏈接:http://madebyevan.com/obscure-cpp-features/

譯文鏈接:http://blog.jobbole.com/54140/

責(zé)任編輯:陳四芳 來源: 伯樂在線
相關(guān)推薦

2011-11-14 09:56:17

C++

2010-01-11 13:37:31

C++語言

2021-06-16 07:56:48

C++新特性類型

2010-01-15 17:38:37

C++語言

2010-01-25 18:19:17

C++特性

2010-01-25 18:19:17

C++特性

2010-01-25 18:19:17

C++特性

2009-09-18 09:59:39

C# CLR

2010-01-22 15:30:36

C++語言

2010-01-15 14:46:20

C++語言

2011-01-05 11:12:34

C++

2012-09-03 16:31:34

Firefox 15瀏覽器

2024-05-27 16:27:22

2013-07-29 11:11:33

C++C++11

2010-01-21 16:24:02

C++語言

2015-02-04 10:49:13

Visual C++C++Windows API

2010-01-22 10:26:40

C++語言

2010-01-13 17:04:36

C++語言

2010-01-14 10:23:08

C++語言

2010-01-26 10:27:43

C++語言
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號