T a(v);和T a = v;的區別

本文所說的類型T均指UDT,非built-in類型

構造一個對象,有如下三種形式:

1。T a;
這個沒什麼好說的,調用default ctor來構造a
不過要注意的是,要麼T就一個ctor也沒有,編譯器合成default ctor,即T::T()
如果T有手動添加的其他形式的ctor,但是沒有T::T(),則此語句報錯,因爲編譯器不再
爲T合成default ctor
*注1,如果沒有default ctor,但是有某個ctor的所有參數都有缺省值,則T a;也成立


2。T a(v);
這個語句顯式用v作爲參數調用T的某個最適合的可單參數調用的ctor來構造a
該語句形式被C++標準稱爲direct-initialization
這裏強調兩點,一個是顯式,這說明這種方式構造a的話可以調用被explicit聲明的ctor
第二點是最適合,這是由overload rules決定的,而不是那麼想當然,示例代碼如下
struct T
{
  T(){}
  T(int){}
  operator int(){return 0;}
private:
  T(T&){}
};
T foo() {return T();}
int main()
{
  T a;

  T b(a);    // 編譯出錯,最適合的T(T&)不可訪問,爲private

  T c(foo());// 編譯成功,foo()返回的臨時對象是rvalue,不能綁定到non-const引用
             // 因此T(T&)不是由overload rules選定的最適合ctor
             // 但是rvalue可以調用一次自定義隱式轉換cast到int然後調用T(int)構造c
             // 也就是說,overload rules選擇了T(int)

  return 0;
}
這段代碼說明,即使類型相同,調用的也不一定是copy ctor
如果有人懷疑T(T&)不是copy ctor,認爲T(const T&)纔是,請參閱C++標準12.8


3。T a = v;
該語句形式被C++標準稱爲copy-initialization,這個名稱有很大的迷惑性,導致本人曾
一度認爲該式語義上(稍後解釋什麼是語義上)必須要調用copy ctor
參照C++標準8.5/14,有這麼一句話
If the destination type is a (possibly cv-qualified) class type:
--If the initialization is direct-initialization, or if it is 
  copy-initialization where the cv-unqualified version of the 
  source type is the same class as, or a derived class of, the 
  class of the destination, constructors are considered. The 
  applicable constructors are enumerated (13.3.1.3), and the 
  best one is chosen through overload resolution (13.3). The 
  constructor so selected is called to initialize the object, 
  with the initializer expression(s) as its argument(s). If no 
  constructor applies, or the overload resolution is ambiguous, 
  the initialization is ill-formed.
簡單點說,上面的意思就是,如果T是UDT,在“copy-initialization且v的cv-
unqualified類型也爲T(或者T的派生類)”的情況下,執行方式同
direct-initialization一樣!
*注2,實際上該情況中的copy-initialization同direct-initialization還是有區別的,
*     C++標準指出,只有T a(v);的方式纔是顯式調用ctor,否則爲隱式調用,後者要求
*     被選中的ctor不能被聲明爲explicit,否則編譯出錯
*---C++標準12.3.1
*     An explicit constructor constructs objects just like non-explicit
*     constructors, but does so only where the direct-initialization
*     syntax (8.5) or where casts (5.2.9, 5.4) are explicitly used.
C++標準還規定,如果非上述情況,則有
--Otherwise (i.e., for the remaining copy-initialization cases), 
  user-defined conversion sequences that can convert from the source
  type to the destination type or (when a conversion function is used) 
  to a derived class thereof are enumerated as described in 13.3.1.4, 
  and the best one is chosen through overload resolution (13.3). If the 
  conversion cannot be done or is ambiguous, the initialization is ill-formed.
  The function selected is called with the initializer expression as its 
  argument; if the function is a constructor, the call initializes a 
  temporary of the destination type. The result of the call (which is 
  the temporary for the constructor case) is then used to direct-initialize, 
  according to the rules above, the object that is the destination of 
  the copy-initialization. In certain cases, an implementation is permitted 
  to eliminate the copying inherent in this direct-initialization by 
  constructing the intermediate result directly into the object being 
  initialized; see 12.2, 12.8.
簡單點說,就是語句T a = v;在當v的類型不同於T(也不是T的派生類)的時候,語義上
要先用v構造一個臨時對象T(或者調用自定義轉換隱式cast到T類型或者T的派生類型),
然後顯式調用copy ctor來構造a
之所以強調語義上,是因爲標準說了這個調用可以優化掉,但是語義存在
而強調顯式,則說明copy ctor可以爲explicit
此外需要強調的就是雖然標準在這裏說構造了臨時對象T(v)之後(或者是將v隱式轉換
爲類型T,static_cast<T>(v))再用direct-initialize直接構造a,整個語句總的執行
形式爲T a(T(v));(static_cast<T>(v)用C語法也可記爲T(v)),但是用T(v)所調用的
ctor只能是T的copy ctor,原因是編譯器已經進行了一次自定義的隱式轉換T(v),不能
再進行第二次,所以必須調用對T(v)直接參數匹配的ctor,這就是copy ctor
對上面的句子還有一個補充,就是派生類向基類的cast不算自定義隱式轉換,是自動發
生的,所以還有一種選擇就是T a(U(v));而U是T的派生類!最終調用的仍舊是copy ctor
示例如下
struct T
{
  T(){}
  T(int){}
  operator int(){return 0;}
private:
  explicit T(T&){}
};
int main()
{
  T a = T();  // 編譯通過,相當於隱式調用T a(T());
              // 而重載規則選用了T(int),即T a(static_cast<int>(T()));
              // 如果T::T(int)聲明爲explicit,編譯出錯

  T b = 0;    // 編譯出錯,copy ctor不能訪問,雖然被優化這個調用,但是語義尚存

  return 0;
}

下面再用一個示例補充說明
struct T
{
  T(int){}
  explicit T(const T&){}
};
void foo(T)
{
}
int main()
{
  foo(T(0)); // 編譯出錯,按照*注2,要求隱式拷貝給foo的參數
             // 相當於T t = T(0);

  foo(0);    // 編譯通過,用0構造一個臨時對象T(0),然後顯式拷貝給foo的參數
             // 相當於T t = 0;

  return 0;
}
由於函數的按值傳參和按值返回都是copy-initialization,所以會有如上的結果差異 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章