C++11語言擴展:常規特性

jopen 10年前發布 | 19K 次閱讀 C/C++開發 C/C++

本節內容:auto、decltype、基于范圍的for語句、初始化列表、統一初始化語法和語義、右值引用和移動語義、Lambdas、noexcept防止拋出異常、constexpr、nullptr——一個指針空值常量、復制并再拋出異常、內聯命名空間、用戶自定義數據標識。

auto

推導

  auto x = 7;

在這里因為它的初始化類型我們將得到x的int類型。一般來說,我們可以寫

  auto x = expression;

x的類型我們將會根據初始化表達式“ expression”的類型來自動推導。

當一個變量的類型很難準確的知道或者寫出的時候,用atuo通過初始化表達式的類型進行推導顯然是非常有用的。

參考:

    template<class T> void printall(const vector<T>& v)
    {
        for (auto p = v.begin(); p!=v.end(); ++p)
            cout << *p << "\n";
    }

在C++98里我們必須這樣寫

 template<class T> void printall(const vector<T>& v)
    {
        for (typename vector<T>::const_iterator p = v.begin(); p!=v.end(); ++p)
            cout << *p << "\n";
    }

當一個變量的類型取決于模板實參的時候不用auto真的很難定義,例如:

template<class T, class U> void multiply(const vector<T>& vt, const vector<U>& vu)
    {
        // ...
        auto tmp = vt[i]*vu[i];
        // ...
    }

 

從T*U的表達式,我們人類的思維是很難理清tmp的類型的,但是編譯器肯定知道T和U經過了什么特殊處理。

auto的特性在最早提出和應用時是有區別的:1984年初,Stroustrup在他的Cfront實現里用到了它,但是是由于C的兼容問題被迫拿來用的,這些兼容的問題已經在C++98和C++99接受了隱性int時候消失了。也就是說,現在這兩種語言要求每一個變量和函數都要有一個明確的類型定義。auto舊的含義(即這是一個局部變量)現在是違法的。標準委員會的成員們在數百萬行代碼中僅僅只找到幾百個用到auto關鍵字的地方,并且大多數出現在測試代碼中,有的甚至就是一個bug。

auto在代碼中主要是作為簡化工具,并不會影響標準庫規范。

參考:

decltype

decltype(E)的類型(“聲明類型”)可用名稱或表達E來聲明。例如:

  void f(const vector<int>& a, vector<float>& b)
    {
        typedef decltype(a[0]*b[0]) Tmp;
        for (int i=0; i<b.size(); ++i) {
            Tmp* p = new Tmp(a[i]*b[i]);
            // ...
        }
        // ...
    }

這個概念已經流行在泛型編程的標簽“typeof”很長一段時間,但實際使用的“領域”的實現是不完整和不兼容,所以標準版命名了decltype。

注意:喜歡使用auto時,你只是需要一個變量的類型初始化。你真的需要decltype如果你需要一個類型的東西不是一個變量,例如返回類型。

參考:

  • the C++ draft 7.1.6.2 Simple type specifiers
  • [Str02] Bjarne Stroustrup. Draft proposal for “typeof”. C++ reflector message c++std-ext-5364, October 2002. (original suggestion).
  • [N1478=03-0061] Jaakko Jarvi, Bjarne Stroustrup, Douglas Gregor, and Jeremy Siek: Decltype and auto (original proposal).
  • [N2343=07-0203] Jaakko Jarvi, Bjarne Stroustrup, and Gabriel Dos Reis: Decltype (revision 7): proposed wording.

基于范圍的for循環

聲明的范圍像是STL-sequence定義的begin()和end(),允許你在這個范圍內循環迭代。所有標準容器可以作為一個范圍來使用,比如可以是std::string,初始化器列表,一個數組,和任何你可以定義begin()和end()的,比如istream。例如:

void f(vector<double>& v)
{
    for (auto x : v) cout << x << '\n';
    for (auto& x : v) ++x;  // using a reference to allow us to change the value
}

你可以看到,V中所有的元素都從begin()開始迭代循環到了end()。另一個例子:

   for (const auto x : { 1,2,3,5,8,13,21,34 }) cout << x << '\n';

begin()(和end())可以被做為x.begin()的成員或一個獨立的函數被稱為開始(x)。成員版本優先。

參考:

初始化列表

推導

 vector<double> v = { 1, 2, 3.456, 99.99 };
    list<pair<string,string>> languages = {
        {"Nygaard","Simula"}, {"Richards","BCPL"}, {"Ritchie","C"}
    }; 
    map<vector<string>,vector<int>> years = {
        { {"Maurice","Vincent", "Wilkes"},{1913, 1945, 1951, 1967, 2000} },
        { {"Martin", "Ritchards"}, {1982, 2003, 2007} }, 
        { {"David", "John", "Wheeler"}, {1927, 1947, 1951, 2004} }
    };

初始化列表不再只針對于數組了。定義一個接受{}初始化列表的函數(通常是初始化函數)接受一個std::initializer_list < T >的參數類型,例如:

    void f(initializer_list<int>);
    f({1,2});
    f({23,345,4567,56789});
    f({});  // the empty list
    f{1,2}; // error: function call ( ) missing
    years.insert({{"Bjarne","Stroustrup"},{1950, 1975, 1985}});

初始化器列表可以是任意長度的,但必須同種類型的(所有元素必須的模板參數類型,T,或可轉換T)。

一個容器可能實現一個初始化列表構造函數如下:

  template<class E> class vector {
    public:
        vector (std::initializer_list<E> s) // initializer-list constructor
        {
                reserve(s.size());  // get the right amount of space
                uninitialized_copy(s.begin(), s.end(), elem);   // initialize elements (in elem[0:s.size()))
            sz = s.size();  // set vector size
        }
        // ... as before ...
    };

直接初始化和復制初始化的區別是對初始化列表的維護,但是因為初始化列表的相關聯的頻率就降低了。例如std::vector有一個int類型顯示構造函數和initializer_list構造函數:

 vector<double> v1(7);   // ok: v1 has 7 elements
    v1 = 9;                 // error: no conversion from int to vector
    vector<double> v2 = 9;  // error: no conversion from int to vector
    void f(const vector<double>&);
    f(9);                           // error: no conversion from int to vector
    vector<double> v1{7};           // ok: v1 has 1 element (with its value 7.0)
    v1 = {9};                       // ok v1 now has 1 element (with its value 9.0)
    vector<double> v2 = {9};        // ok: v2 has 1 element (with its value 9.0)
    f({9});                         // ok: f is called with the list { 9 }
    vector<vector<double>> vs = {
        vector<double>(10),         // ok: explicit construction (10 elements)
        vector<double>{10},         // ok explicit construction (1 element with the value 10.0)
        10                          // error: vector's constructor is explicit
    };

函數可以作為一個不可變的序列訪問initializer_list。例如:

void f(initializer_list<int> args)
    {
        for (auto p=args.begin(); p!=args.end(); ++p) cout << *p << "\n";
    }

僅具有一個std::initializer_list的單參數構造函數被稱為初始化列表構造函數。

標準庫容器,string類型及正則表達式均具有初始化列表構造函數,以及(初始化列表)賦值函數等。一個初始化列表可被用作Range,例如,表達式Range。

初始化列表是一致泛化初始化解決方案的一部分。他們還防止類型收窄。一般來說,你應該通常更喜歡使用{ }來代替()初始化,除非你想用c++98編譯器來分享代碼或(很少)需要使用()調用沒initializer_list重載構造函數。

參考:

統一初始化語法和語義

c++ 98提供了幾種方法初始化一個對象根據其類型和初始環境。濫用時,會產生可以令人驚訝的錯誤和模糊的錯誤消息。推導:

  string a[] = { "foo", " bar" };          // ok: initialize array variable
    vector<string> v = { "foo", " bar" };    // error: initializer list for non-aggregate vector
    void f(string a[]);
    f( { "foo", " bar" } );                  // syntax error: block as argument

    int a = 2;              // "assignment style"
    int aa[] = { 2, 3 };    // assignment style with list
    complex z(1,2);         // "functional style" initialization
    x = Ptr(y);             // "functional style" for conversion/cast/construction

  int a(1);   // variable definition
    int b();    // function declaration
    int b(foo); // variable definition or function declaration

要記得初始化的規則并選擇最好的方法去初始化是比較難的。

C++11的解決方法是允許所有的初始化使用初始化列表

   X x1 = X{1,2}; 
    X x2 = {1,2};   // the = is optional
    X x3{1,2}; 
    X* p = new X{1,2}; 
    struct D : X {
        D(int x, int y) :X{x,y} { /* ... */ };
    };
    struct S {
        int a[3];
        S(int x, int y, int z) :a{x,y,z} { /* ... */ }; // solution to old problem
    };

重點是,x{a}在在執行代碼中都創建了一個相同的值,所以在使用“{}”進行初始化合法的情況下都產生了相同的結果。例如:

   X x{a}; 
    X* p = new X{a};
    z = X{a};         // use as cast
    f({a});           // function argument (of type X)
    return {a};       // function return value (function returning X)

參考:

右值引用和移動語義

左值(用在復制操作符左邊)和右值(用在復制操作符右邊)的區別可以追溯到Christopher Strachey (C++遙遠的祖先語言CPL和外延語義之父)的時代。在C++中,非const引用可以綁定到左值,const引用既可以綁定到左值也可以綁定要右值。但是右值卻不可以被非const綁定。這是為了防止人們改變那些被賦予新值之前就被銷毀的臨時變量。例如:

void incr(int& a) { ++a; }
    int i = 0;
    incr(i);    // i becomes 1
    incr(0);    // error: 0 in not an lvalue

如果incr(0)被允許,那么就會產生一個無法被人看到的臨時變量被執行增加操作,或者更糟的0會變成1.后者聽起來很傻,但實際上確實存在這樣一個bug在Fortran編譯器中:為值為0的內存位置分配。

到目前為止還好,但考慮以下代碼:

 template<class T> swap(T& a, T& b)      // "old style swap"
    {
        T tmp(a);   // now we have two copies of a
        a = b;      // now we have two copies of b
        b = tmp;    // now we have two copies of tmp (aka a)
    }

如果T是一個復制元素要付出昂貴代價的類型,比如string和vector,swap將會變成一個十分昂貴的操作(對于標準庫來說,我們有專門化的string和vector來處理)。注意一下這些奇怪的現象:我們并不想任何變量拷貝。我們僅僅是想移動變量a,b和tmp的值。

在C++11中,我們可以定義“移動構造函數”和“移動賦值操作符”來移動,而不是復制他們的參數:

    template<class T> class vector {
        // ...
        vector(const vector&);          // copy constructor
        vector(vector&&);           // move constructor
        vector& operator=(const vector&);   // copy assignment
        vector& operator=(vector&&);        // move assignment
    };  // note: move constructor and move assignment takes non-const &&
        // they can, and usually do, write to their argument

& &表明“右值引用”。一個右值引用可以綁定到一個右值(而不是一個左值):

  X a;
    X f();
    X& r1 = a;      // bind r1 to a (an lvalue)
    X& r2 = f();        // error: f() is an rvalue; can't bind
    X&& rr1 = f();  // fine: bind rr1 to temporary
    X&& rr2 = a;    // error: bind a is an lvalue

賦值這個操作的背后思想,并不是拷貝,它只是構造一個源對象的代表然后再替換。例如,string s1 = s2的移動,它不是產生s2的拷貝,而是讓s1把s2中字符變為自己的同時刪除自己原有的字符串(也可以放在s2中,但是它也面臨著被銷毀)

我們如何知道是否可以簡單的從源對象進行移動?我們可以告訴編譯器:

    template<class T> 
    void swap(T& a, T& b)   // "perfect swap" (almost)
    {
        T tmp = move(a);    // could invalidate a
        a = move(b);        // could invalidate b
        b = move(tmp);      // could invalidate tmp
    }

move(x)只是意味著你“你可以把x當作一個右值”,

如果把move()稱做eval()也許會更好,但是現在move()已經用了好多年了。在c++11中,move()模板(參考簡介)和右值引用都可以使用。

右值引用也可以用來提供完美的轉發。

在C++0x的標準庫中,所有的容器都提供了移動構造函數和移動賦值操作符,那些插入新元素的操作,如insert()和push_back(), 也都有了可以接受右值引用的版本。最終結果是,在無用戶干預時,標準容器和算法的性能都提升了,因為復制操作的減少。

參考:

lambdas

Lambda表達式是一種描述函數對象的機制,它的主要應用是描述某些具有簡單行為的函數(譯注:Lambda表達式也可以稱為匿名函數,具有復雜行為的函數可以采用命名函數對象,當然,簡單和復雜之間的劃分依賴于編程人員的選擇)。例如:

    vector<int> v = {50, -10, 20, -30};
    std::sort(v.begin(), v.end());  // the default sort
    // now v should be { -30, -10, 20, 50 }
    // sort by absolute value:
    std::sort(v.begin(), v.end(), [](int a, int b) { return abs(a)<abs(b); });
    // now v should be { -10, 20, -30, 50 }

參數 [&](int a, int b) { return abs(a) < abs(b); }是一個"lambda"(又稱為"lambda函數"或者"lambda表達式"), 它描述了這樣一個函數操作:接受兩個整形參數a和b,然后返回對它們的絕對值進行"<"比較的結果。(譯注:為了保持與代碼的一致性,此處應當為"[] (int a, int b) { return abs(a) < abs(b); }",而且在這個lambda表達式內實際上未用到局部變量,所以 [&] 是無必要的)

一個Lambda表達式可以存取在它被調用的作用域內的局部變量。例如:

 void f(vector<Record>& v)
    {
        vector<int> indices(v.size());
        int count = 0;
        generate(indices.begin(),indices.end(),[&count](){ return count++; });
        // sort indices in the order determined by the name field of the records:
        std::sort(indices.begin(), indices.end(), [&](int a, int b) { return v[a].name<v[b].name; });
        // ...
    }

有人認為這“相當簡潔”,也有人認為這是一種可能產生危險且晦澀的代碼的方式。我的看法是,兩者都正確。

[&] 是一個“捕捉列表(capture list)”,用于描述將要被lambda函數以引用傳參方式使用的局部變量。如果我們僅想“捕捉"參數v,則可以寫為: [&v]。而如果我們想以傳值方式使用參數v,則可以寫為:[=v]。如果什么都不捕捉,則為:[]。將所有的變量以引用傳遞方式使用時采用 [&], [=] 則相應地表示以傳值方式使用所有變量(譯注:“所有變量”即指lambda表達式在被調用處,所能見到的所有局部變量)。

如果某一函數的行為既不通用也不簡單,那么我建議采用命名函數對象或者函數。例如,如上示例可重寫為:

 void f(vector<Record>& v)
    {
        vector<int> indices(v.size());
        int count = 0;
        generate(indices.begin(),indices.end(),[&](){ return ++count; });
        struct Cmp_names {
            const vector<Record>& vr;
            Cmp_names(const vector<Record>& r) :vr(r) { }
            bool operator()(int a, int b) const { return vr[a].name<vr[b].name; }
        };
        // sort indices in the order determined by the name field of the records:
        std::sort(indices.begin(), indices.end(), Cmp_names(v));
        // ...
    }

對于簡單的函數功能,比如記錄名稱域的比較,采用函數對象就略顯冗長,盡管它與lambda表達式生成的代碼是一致的。在C++98中,這樣的函數對象在被用作模板參數時必須是非本地的(譯注:即你不能在函數對象中像此處的lambda表達式那樣使用被調用處的局部變量),然而在C++中(譯注:意指C++0x),這不再是必須的。

為了描述一個lambda,你必須提供:

  • 它的捕捉列表:它可以使用的變量列表(除了形參之外),如果存在的話("[&]" 在上面的記錄比較例子中意味著“所有的局部變量都將按照引用的方式進行傳遞”)。如果不需要捕捉任何變量,則使用 []。
  • (可選的)它的所有參數及其類型(例如: (int a, int b) )。
  • 組織成一個塊的函數行為(例如:{ return v[a].name < v[b].name; })。
  • (可選的)采用了新的后綴返回類型符號的返回類型。但典型情況下,我們僅從return語句中去推斷返回類型,如果沒有返回任何值,則推斷為void。

參考:

noexcept防止拋出異常

如果一個函數不能拋出異常或者一個程序沒有對函數拋出的異常進行處理,那么這個函數可以用關鍵字noexcept進行修飾,例如:

 extern "C" double sqrt(double) noexcept;    // will never throw
    vector<double> my_computation(const vector<double>& v) noexcept // I'm not prepared to handle memory exhaustion
    {
        vector<double> res(v.size());   // might throw
        for(int i; i<v.size(); ++i) res[i] = sqrt(v[i]);
        return res;
    }

如果一個被noexcept修飾的函數拋出了異常(所以異常會跳出唄noexcept修飾的函數),程序會調用std::terminate()這個函數來終止程序。在對象被明確定義的狀態下不能調用terminate();比如無法保證析構函數正常調用,不能保證棧的自動釋放,也無法保證在遇到任何問題時重新啟動。故意這樣的使noexcept成為一種簡單“粗暴”而有效的處理機制-它比舊的處理機制throw()動態拋出異常要有效的多。

它可以讓一個函數根據條件來實現noexcept修飾。比如,一個算法可以根據他的模板參數來決定自己是否拋出異常。

 template<class T>
    void do_f(vector<T>& v) noexcept(noexcept(f(v.at(0)))) // can throw if f(v.at(0)) can
    {
        for(int i; i<v.size(); ++i)
            v.at(i) = f(v.at(i));
    }

這里,第一個noexcept被用作操作符operator:如果if f(v.at(0))不能夠拋出異常,noexcept(f(v.at(0)))則返回true,所以f()和at()是無法拋出異常noexcept。

noexcept()操作符是一個常量表達式,并且不計算表達式的值。

聲明的通常形式是noexcept(expression),并且單獨的一個“noexcept”關鍵字實際上就是的一個noexcept(true)的簡化。一個函數的所有聲明都必須與noexcept聲明保持 兼容。

一個析構函數不應該拋出異常;通常,如果一個類的所有成員都擁有noexcept修飾的析構函數,那么這個類的析構函數就自動地隱式地noexcept聲明,而與函數體內的代碼沒有關系。

通常,將某個拋出的異常進行移動操作是一個很壞的主意,所以,在任何可能的地方都用noexcept進行聲明。如果某個類的所有成員都有使用noexcept聲明的析構函數,那么這個類默認生成的復制或者移動操作(類的復制構造函數,移動構造函數等)都是隱式的noexcept聲明。(?)

noexcept 被廣泛地系統地應用在C++11的標準庫中,以此來提供標準庫的性能和滿足標準庫對于簡潔性的需求。

參考:

constexpr

常量表達式機制:

  • 提供了更多的通用的值不發生變化的表達式
  • 允許用戶自定義的類型成為常量表達式
  • 提供了一種保證在編譯期完成初始化的方法

考慮下面這段代碼:

    enum Flags { good=0, fail=1, bad=2, eof=4 };
    constexpr int operator|(Flags f1, Flags f2) { return Flags(int(f1)|int(f2)); }
    void f(Flags x)
    {
        switch (x) {
        case bad:         /* ... */ break;
        case eof:         /* ... */ break;
        case bad|eof:     /* ... */ break;
        default:          /* ... */ break;
        }
    }

在這里,常量表達式關鍵字constexpr表示這個重載的操作符“|”就應該像一個簡單的表單一樣,如果它的參數本身就是常量 ,那么這個操作符應該在編譯時期就應該計算出它的結果來。
除了可以在編譯時期被動地計算表達式的值之外,我們希望能夠主動地要求表達式在編譯時期計算其結果值,從而用作其它用途,比如對某個變量進行賦值。當我們在變量聲明前加上constexpr關鍵字之后,可以實現這一功能,當然,它也同時會讓這個變量成為常量。

 constexpr int x1 = bad|eof; // ok
    void f(Flags f3)
    {
        constexpr int x2 = bad|f3;  // error: can't evaluate at compile time
        int x3 = bad|f3;        // ok
    }

通常,我們希望編譯時期計算可以保護全局或者名字空間內的對象,對名字空間內的對象,我們希望它保存在只讀空間內。
對于那些構造函數比較簡單,可以成為常量表達式(也就是可以使用constexpr進行修飾)的對象可以做到這一點(?)

  struct Point {
        int x,y;
        constexpr Point(int xx, int yy) : x(xx), y(yy) { }
    };
    constexpr Point origo(0,0);
    constexpr int z = origo.x;
    constexpr Point a[] = {Point(0,0), Point(1,1), Point(2,2) };
    constexpr int x = a[1].x;   // x becomes 1
  • const的主要功能是修飾一個對象而不是通過一個接口(即使對象很容易通過其他接口修改)。只不過聲明一個對象常量為編譯器提供了優化的機會。特別是,如果一個聲明了一個對象常量而他的地址沒有取到,編譯器通常可以在編譯時對他進行初始化(盡管這不是肯定的)保證這個對象在他的列表里而不是把它添加到生成代碼里。
  • constexpr的主要功能可以在編譯時計算表達式的值進行了范圍擴展,這是一種計算安全而且可以用在編譯時期(如初始化枚舉或者整體模板參數)。constexpr聲明對象可以在初始化編譯的時候計算出結果來。他們基本上只保存在編譯器的列表,如果需要的話會釋放到生成的代碼里。

參考:

nullptr 一個指針空值常量

nullptr是一個指針空值常量,不是一個整數。

   char* p = nullptr;
    int* q = nullptr;
    char* p2 = 0;           // 0 still works and p==p2
    void f(int);
    void f(char*);
    f(0);                   // call f(int)
    f(nullptr);             // call f(char*)
    void g(int);
    g(nullptr);             // error: nullptr is not an int
    int i = nullptr;        // error: nullptr is not an int

參考:

復制并再拋出異常

你如何捕獲一個異常然后把它拋出到另一個線程?使用標準文檔18.8.5里描述的標準庫的魔力方法吧。

exception_ptr current_exception(); 正在處理的異常(15.3)或者正在處理的異常的副本(拷貝)將返回一個exception_ptr 變量,如果當前沒有遇到異常,返回值為一個空的exception_ptr變量。只要exception_ptr指向一個異常,那么至少在exception_ptr的生存期內,運行時能夠保證被指向的異常是有效的。

void rethrow_exception(exception_ptr p);
template exception_ptr copy_exception(E e); 它的作用如同:

 try {
        throw e;
    } catch(...) {
        return current_exception();
    }

當我們需要將異常從一個線程傳遞到另外一個線程時,這個方法十分有用的。

內聯命名空間

內聯命名空間機制是通過一種支持版本更新的機制來支持庫的演化,推導:

 // file V99.h:
    inline namespace V99 {
        void f(int);    // does something better than the V98 version
        void f(double); // new feature
        // ...
    }
    // file V98.h:
    namespace V98 {
        void f(int);    // does something
        // ...
    }
    // file Mine.h:
    namespace Mine {
    #include "V99.h"
    #include "V98.h"
    }

我們這里有一個命名空間Mine包含最新版本的(V99)和前一個版本(V98),如果你想要顯式應用(某個版本的函數),你可以:

 #include "Mine.h"
    using namespace Mine;
    // ...
    V98::f(1);  // old version
    V99::f(1);  // new version
    f(1);       // default version

內聯的關鍵是使內聯命名空間的聲明和直接在外圍命名空間聲明一樣。

lnline是靜態的及面向實現的設施,它由命名空間的設計者放置來幫助用戶進行選擇。對于Mine的用是不可以說“我想要的命名空間是V98而不是V99”。

參照:

  • Standard 7.3.1 Namespace definition [7]-[9].

用戶自定義數據標識

C++提供了很多內置的數據標識符(2.14節變量)

built-in types (2.14 Literals):

  123 // int
    1.2 // double
    1.2F    // float
    'a' // char
    1ULL    // unsigned long long
    0xD0    // hexadecimal unsigned
    "as"    // string

然而,愛C++98里并沒有用戶自定義的數據標識符。這就有悖于甚至沖突“用戶自定義類型和內置leiixng一樣得到支持”的原則。特殊情況下,人們有這樣的需求:

 "Hi!"s          // std::string, not ``zero-terminated array of char''
    1.2i            // imaginary
    123.4567891234df    // decimal floating point (IBM)
    101010111000101b    // binary
    123s            // seconds
    123.56km        // not miles! (units)
    1234567890123456789012345678901234567890x   // extended-precision

C++11支持“用戶自定義數據標識”通過在變量名后面加一個后綴來標定所需類型,例如:

constexpr complex<double> operator "" i(long double d)  // imaginary literal
    {
        return {0,d};   // complex is a literal type
    }
    std::string operator""s (const char* p, size_t n)   // std::string literal
    {
        return string(p,n); // requires free store allocation
    }

注意constexpr的使用可以在編譯時期計算。有了這個功能,我們可以這樣寫:

    template<class T> void f(const T&);
    f("Hello"); // pass pointer to char*
    f("Hello"s);    // pass (5-character) string object
    f("Hello\n"s);  // pass (6-character) string object
    auto z = 2+1i;  // complex(2,1)

基本(實現)方法是編譯器在解析什么語句代表一個變量之后,再分析一下后綴。用戶自定義數據標識機制只是簡簡單單的允許用戶制定一個新的后綴,并決定如何對它之前的數據進行處理。要想重新定義一個內建的數據標識的意義或者它的參數、語法是不可能的。一個數據標識操作符可以使用它(前面)的數據標識傳遞過來的處理過的值(如果是使用新的沒有定義過的后綴的值)或者沒有處理過的值(作為一個字符串)。
要得到一個沒有處理過的字符串,只要使用一個單獨的const char*參數即可,例如:

  Bignum operator"" x(const char* p)
    {
        return Bignum(p);
    }
    void f(Bignum);
    f(1234567890123456789012345678901234567890x);

這個C語言風格的字符串"1234567890123456789012345678901234567890"被傳遞給了操作符 operator"" x()。注意,我們并沒有明確地把數字轉換成字符串。

有以下四種數據標識的情況,可以被用戶定義后綴來使用用戶自定義數據標識:

  • 整型標識:允許傳入一個unsigned long long或者const char*參數
  • 浮點型標識:允許傳入一個long double或者const char*參數
  • 字符串標識:允許傳入一組(const char*,size_t)參數
  • 字符標識:允許傳入一個char參數。

注意,你為字符串標識定義的標識操作符不能只帶有一個const char*參數(而沒有大小)。例如:

 string operator"" S(const char* p);     // warning: this will not work as expected
    "one two"S; // error: no applicable literal operator

根本原因是如果我們想有一個“不同的字符串”,我們同時也想知道字符的個數。后綴可能比較短(例如,s是字符串的后綴,i是虛數的后綴,m是米的后綴,x是擴展類型的后綴),所以不同的用法很容易產生沖突,我們可以使用namespace(命名空間)來避免這些名字沖突:

  namespace Numerics { 
        // ...
        class Bignum { /* ... */ }; 
        namespace literals { 
            operator"" X(char const*); 
        } 
    } 
    using namespace Numerics::literals;

參考:

原文鏈接: isocpp   翻譯: 伯樂在線 - christian
譯文鏈接: http://blog.jobbole.com/55063/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!