【C++札記】C++的直接初始化與複製初始化

2021-03-02 碼農有道

作者:負一的平方根

連結:https://sqrt-1.me/?p=241

C++中的直接初始化指的是直接調用類的構造函數進行初始化,例如:

複製初始化指的是用「=」號來初始化對象,例如:

在上面的例子中,這兩種寫法是完全等效的,但是直接初始化和複製初始化在一些情況下還是有區別的。

根據C++的標準,直接初始化就是直接調用類的構造函數來初始化對象,例如在string a(「hello」)中,string類的string(const char *)構造函數會被調用,a被直接初始化。

然而根據標準,複製初始化應該是先調用對應的構造函數創建一個臨時對象,再調用拷貝構造函數將臨時對象拷貝給要創建的對象。例如在string a=」hello」中,string類的string(const char *)構造函數會被首先調用,創建一個臨時對象,然後拷貝構造函數將這個臨時對象複製到a。

標準還規定,為了提高效率,允許編譯器跳過創建臨時對象這一步,直接調用構造函數構造要創建的對象,這樣就完全等價於直接初始化了。

經過我的測試,常見的編譯器(gcc和VC++)都會直接跳過創建臨時對象這一步,即使沒有打開任何優化選項。

這樣一來,也許你就會認為他們完全等同了,其實不然。下面分析兩種情況。

1、當拷貝構造函數為private時。

雖然編譯器會跳過創建臨時對象這一步,但是這個類必須要能夠正確的調用指定的構造函數和拷貝構造函數才能編譯通過。優化是編譯器採取的措施,但是程序必須使得不優化時代碼也符合語法。

C++中有一些系統類型不允許複製,例如iostream和fstream這樣的類。如果你想把自定義的類設置為不允許複製,把拷貝構造函數和賦值運算符聲明為private即可,前面所說的系統類型也是這樣做的。

我們用ifstream舉例說明直接初始化和複製初始化的區別。用一個字符串來構造ifstream對象,即打開以字符串為文件名的文件輸入流。

我們來分析一下為什麼第二個語句會編譯出錯。根據C++標準,第二個語句應該做以下兩件事情:用」filename」初始化一個臨時的ifstream對象,把臨時的對象用拷貝構造函數複製給file。由於拷貝構造函數是private,所以沒有權限調用此函數,編譯出錯。

註:Visual Studio的C++編譯器並沒有遵循標準,而是不考慮拷貝構造函數是否為private,直接編譯通過。

2、當拷貝構造函數為explicit,或者指定的構造函數為explicit時。

C++中如果一個構造函數為explicit,那麼只能顯式調用這個構造函數,把這個構造函數用作「隱式類型轉換」是不可以的。

我們定義一個類A,然後進行以下測試

可以編譯通過。然後我們把參數為int的構造函數改為explicit,代碼如下

「A a(1)」可以編譯通過,「A b=1」這句報錯,錯誤信息是「conversion from 『int』 to non-scalar type 『A』 requested」。這意味著編譯器找不到int類型轉換成A類型的轉換函數了。如果不加explicit,參數為int的構造函數可以用於自動把int轉換為A類型,但是現在不可以了。第一句的「A a(1)」有參數為int的構造函數的顯式調用,而第二句「A b=1」沒有顯式調用。

下面我們只把拷貝構造函數改成explicit

還是第二句編譯錯誤。錯誤信息是「no matching function for call to 『A::A(A)』」,這說明編譯器找不到拷貝構造函數了。和前面第一種情況中說明的一樣,編譯器在把臨時對象複製到b中時,需要調用A類的拷貝構造函數。現在拷貝構造函數是explicit的,只能顯式的調用,不能隱含調用。第二句沒有「A b(temp)」這樣顯式複製對象,所以編譯出錯。

推薦閱讀:

精心整理 | 2017下半年文章目錄

薦號 | 幾個用心的技術公眾號

你所不知道的TIME_WAIT(下)

神經網絡淺講:從神經元到深度學習(二)

專注伺服器後臺技術棧知識總結分享

歡迎關注交流共同進步

碼農有道,為您提供通俗易懂的技術文章,讓技術變的更簡單!

相關焦點

  • C++ 列表初始化詳解
    以下是正文統一的初始化方法在C++98/03中我們只能對普通數組和POD(plain old data,簡單來說就是可以用memcpy複製的對象)類型可以使用列表初始化,如下:數組的初始化列表:int arr[3] = {1,2,3}POD類型的初始化列表:
  • c++11新特性,所有知識點都在這了!
    返回值優化:當函數需要返回一個對象實例時候,就會創建一個臨時對象並通過複製構造函數將目標對象複製到臨時對象,這裡有複製構造函數和析構函數會被多餘的調用到,有代價,而通過返回值優化,C++標準允許省略調用這些複製構造函數。
  • C++初始化的坑你也遇到過嗎?
    談及C++的初始化,我們都知道要在變量定義的時候給它賦初值。確實,在每次定義的時候就初始化不僅可以避免髒數據產生,還能增加代碼的可讀性。
  • C++機器學習庫介紹
    +11 -lboost_serialization -lshark -lcblas用Shark實現線性回歸初始化階段我們將從包含線性回歸的庫和頭函數開始:#include <bits/stdc++.h> //所有c++標準庫的頭文件#include <shark/
  • 【C++札記】RAII慣用法:C++資源管理的利器
    作者:Andrew連結:cnblogs.com/hsinwang/articles/214663.html碼農有道作了部分修改最近在學習c++
  • C++ | 虛函數簡介
    本文將簡單探究一下 c++ 中的虛函數實現機制。本文主要基於 vs2013 生成的 32 位代碼進行研究,相信其它編譯器(比如,gcc)的實現大同小異。先從對象大小開始 假設我們有如下代碼,假設 int 佔 4 字節,指針佔 4 字節。
  • C++機器學習庫介紹 | 文末送書
    +11 -lboost_serialization -lshark -lcblas用Shark實現線性回歸初始化階段#include <bits/stdc++.h> //所有c++標準庫的頭文件#include <shark/Data/Csv.h> //導入csv數據的頭文件#include <shark
  • C++ initializer_list 詳解
    template< class T >class initializer_list;要介紹initializer_list的使用,有必要先談一談列表初始化。C++11擴大了初始化列表的適用範圍,使其可用於所有內置類型和用戶定義的類型。
  • C 2 C++進階篇(1)
    之前一直是對於面向過程的編程,python有過那種對象風格的編程,但是對於oop的實際開發還停留在表面,沒有獨立的開發c++經驗,也有好幾年沒有碰過c了。由於接手Qt的相關項目,所以對c to c++的進階希望能進行個自我總結。
  • C++ 的門門道道 | 技術頭條 - CSDN
    二、編譯器為什麼不給局部變量和成員變量做默認初始化?因為效率,C++被設計為系統級的程式語言,效率是優先考慮的方向,c++秉持的一個設計哲學是不為不必要的操作付出任何額外的代價,所以它有別於java,不給成員變量和局部變量做默認初始化,如果需要賦初值,那就由程式設計師自己去保證。
  • C++ 的幾個for 循環,範圍for語句
    每次迭代,declaration部分的變量會被初始化為expression部分的下一個元素值。例子:#include <iostream>using namespace std;int main(){ string str("this is a c++"); //每行輸出str中的一個字符 for(
  • 「最佳實踐」C++陷阱與套路
    傳遞性儘量對索引或者指針sort,而不是針對對象本身,因為如果對象比較大,交換(複製)對象比交換指針或索引更耗費。## 3.注意操作符短路考慮遊戲玩家回血回藍(魔法)刷新給客戶端的邏輯。在GetFightValue()裡判斷FightValueDirtyFlag,如果髒,則重算,清髒標記;如果不髒,直接返回之前計算的結果。預計算的思想類似。
  • python+C、C++混合編程的應用
    有的語言專注於簡單高效,比如python,內建的list,dict結構比c/c++易用太多,但同樣為了安全、易用,語言也犧牲了部分性能。在有些領域,比如通信,性能很關鍵,但並不意味這個領域的coder只能苦苦掙扎於c/c++的陷阱中,比如可以使用多種語言混合編程。
  • C 語言會比 C++ 快?
    但是,這些通常與之前版本的simplifier.cpp非常接近,不同之處在於現在可以直接比較變化。我們的代碼中不需要 resize 或 push_back,所有數組都使用正確的大小進行初始化。我們的要求足夠低,我們這種 std::vector 的替代方式僅僅需要 40行代碼 [5],並且主要由 operator[] 定義組成。
  • C++ vector詳解
    正文 2.1 vector基本布局一個簡單的vector,我們可以理解成如下形式,主要是舉了reserve()和resize()這兩個例子,來舉例vector是如何分配內存,創建初始化對象的,以及析構對象的;vector內部管理著一塊內存,當需要push_back對象的時候,會使用這塊內部的內存使用
  • c++ 之布爾類型和引用的學習總結!
    2、c++中的三目運算符可以直接返回變量本身,既可以作為右值使用,也可以作為左值來使用。3、c++中的三目運算符可能返回的值中如果有一個是常量值,則不能作為左值進行使用,這點要切記和理解。--引用的語法:Type &name = var;這裡舉個簡單的示例: int a =4; int& b =a; //b為a的別名 b = 5;//操作b就是操作a--注意普通引用在定義時必須用同類型的變量進行初始化
  • c++之內存分配、命名空間、強制類型轉換學習總結
    一、C++動態內存分配:在學習c語言的時候,我們一般都是使用庫函數malloc()來進行內存的申請分配,然後使用庫函數free()來進行釋放申請到的內存;現在在c++裡面採用了另外一種內存申請的方法:c++中通過
  • Effective C++讀書筆記之拷貝構造函數和成員變量初始化
    構造函數初始化成員變量有兩種方法,一種是通過在構造函數中賦值的方式,另外一種是通過成員初始化列表的方式,兩者初始化方式最大的差別就是後者比前者效率高性能好。這是因為C++在運行過程中首先調用成員變量的默認構造函數,再進入構造函數中。我們拿實際代碼來觀察一下。
  • C++打怪 之 vector
    //頭文件#include <vector> using namespace std;/* 構造方法 */vector<類型>標識符vector<類型>標識符(最大容量)vector<類型>標識符(最大容量,初始所有值)vector(const vector&) 複製
  • 還不懂c++vector的用法,你憑什麼勇氣來的!
    今天給大家帶來一篇c++vector的介紹,難以置信這篇文章寫了我三天,不過總算整理完畢,現在分享給大家。模板類vector 和 array是數組的替代品。模板類vector 類似於string類,也是一種動態數組。 在 c++ 中,vector 是一個十分有用的容器。