搜尋

首頁  >  問答  >  主體

C++中的拷贝初始化

伊谢尔伦伊谢尔伦2807 天前700

全部回覆(5)我來回復

  • 巴扎黑

    巴扎黑2017-04-17 14:20:29

    0x00 總體分析

    編譯錯誤因為

    1. 沒有加 ,就用 printf()

    2. 左值右值,const 概念理解不清,寫出 A(A& x) 這樣的 copy constructor

    按道理從oneMore裡面出來裡面所有的分配的棧上的空間都要被釋放掉的... 搞不懂- -, 難道是編譯器優化的緣故? 直接就跳過了賦值初始化, 同時不釋放那一塊在oneMore中指向b的空間, 達到初始化main裡面這個b的效果嗎?

    是因為你對對於編譯器最佳化了解較少,而且 c++ 標準對此沒有明確規定。

    0x01 左值、右值、const 與 non-const

    首先,你去搜尋一什麼是左值、右值。這是一個首先出現於編譯領域的概念,很容易理解。

    然後,我們來看下面這兩段你寫的程式碼:

        A oneMore(){
            A b;
            return b;
        }
        A b = a.oneMore();

    從一個返回類型為非引用類型的函數返回一個物件並用這個物件對另一個同類型物件賦值初始化,確實應該發生兩次 copy constructor 的調用,返回物件的時候一次, =初始化的時候一次(不過不一定,見0x02)。

    但是函數oneMore()回傳型別是一個右值。右值一般被認為是一個不可被使用者程式碼明確改變的值(除了c++11 中的右值引用和move 建構函式機制)僅可以傳遞給一個左值物件或const 左值引用,這樣才可以保證其內容不被改變。

    一個右值如果傳遞給一個 non-const 左值引用了,那就表示使用者有可能去改變這個右值的值。所以你的編譯器提示你 candidate constructor not viable: expects an l-value for 1st argument A(A& x) 。這裡 l-value 就是左值的意思。你應該改寫成A(const A& a)

    如我沒記錯的話,C++ Primer 應該也有建議創建 copy constructor 的時候一律使用 A(const A& rhs);的形式,避免使用 A(A& rhs)。因為前者能涵蓋後者。

    具體的左值右值,const 與 non-const 規則,你可以 Search The Fucking Web.

    0x02 編譯器最佳化

    A b = a.oneMore();,這個表達式要是不優化,要經歷以下步驟:

    1. 進入 oneMore() 函數體,建立要傳回的物件 oneMore()::b。 (automatic storage, default constructor)

    2. 函數傳回,將物件 oneMore()::b 複製初始化給 main()::xxx。 xxx是一個暫時匿名對象,儲存a.oneMore()的返回值。 (automatic storage, copy constructor)

    3. A b = xxx 用 xxx 賦值初始化 main()::b (automatic storage, copy constructor)

    正好,上面這些 C++ primer 也有講。
    我們來算一算,這短短的一個表達式在不優化的情況下我們消耗了多少計算量:三個constructor,兩個destructor,這要是一個保存數千string的vector。 。 。

    我們先對變數進行拓樸排序:
    oneMore()::b -> xxx -> main()::b
    可以很明顯地看到(即使編譯器也能發現):使用xxx 的時候oneMore()::b 就沒用了,使用main()::b 的時候xxx 就沒用了。所以編譯器會做一些最佳化來消除一些不必要的中間步驟計算。

    優化的過程和演算法就不說了,我們來看優化的結果:最後表達式A b = a.oneMore()整個計算過程中只有一個變數main()::b, 所有的操作都作用在了這個變數上。 oneMore()::b, xxx 可以說根本沒有創建,沒創建就意味著不用銷毀,這就可以省下兩個 constructor 和兩個 desctructor。最後這個表達式需要一個 constructor,零個 destructor

    既然所有操作都作用在同一個物件上了,那麼無論在何處列印這個物件(雖然名字不同)的地址也就是一樣了。都是 0x7fff52bfd9a8

    回覆
    0
  • PHPz

    PHPz2017-04-17 14:20:29

    謝邀。
    其實我已經回答過好幾次樓主的問題了,可能是sf上c++的人不多的原因。 。
    這裡我要說些題外話,關於樓主的學習方法,對於c++來說,如果想要快速理解,寫程式是必須的,但分析問題不是這樣分析的。
    c++是一門相對偏底層的語言,編譯結果直接就是二進制,所以我們一般透過彙編來了解原理是最快速的方法。
    實際上這個問題,幾個月我也剛研究過。

    註:以下程式碼皆為debug模式下,未經最佳化。 (優化的情況下,編譯器會根據類型的複雜程度,進行參數拆分,堆上的指針不經複製直接返回等,這屬於編譯器行為,和c++語言本身無關。)

    std::string test2()
    {
        std::string s="222";
        return s;
    }
    void main()
    {
        std::string s1=test2();
        std::cout<<s1<<std::endl;
    }

    我們知道,堆疊上申請的記憶體一定是隨著函數的回傳而被釋放(因為要平棧),所以上面的函數用組譯來分析,偽代碼大概如下:

    std::string test2()
    {
        //里面执行stirng的构造函数
        auto s = new stringObject("123123");
        //dosomething;
        
        //返回前要平栈,但是这个复杂对象又是一个返回值
        //所以要先复制到返回值的地方
        auto s1=new stringObjectForm(s);
        //复制完成后要对本栈内的string析构,进行内存的释放
        delete s;
        return s1;
    }

    然後回傳值這邊的s1其實是在裡面申請的記憶體。
    至於引用參數傳值和上述過程類似,但不同的是,其是先呼叫方申請記憶體後傳遞入被呼叫方供複製。

    回覆
    0
  • 怪我咯

    怪我咯2017-04-17 14:20:29

    您好,您的程式碼在我的機器上,運作如下,並沒有大的問題。

    您的第二個關於地址的疑問,請見下圖。我笑了....

    本人的編譯環境主要是VC++的,如下

    本人再次使用linux g++編譯器,對您的程式做了測試。
    一點,程式的程式碼風格出現嚴重的錯誤。我修改了不規範的地方,然後才編譯通過。
    g++ xxxx.cpp
    程式運行的結果顯示,位址是正確的。只是沒有呼叫拷貝構造函數。具體原因不詳。
    建議:即使再小的程序,請把程式碼格式規範一下。

    回覆
    0
  • PHPz

    PHPz2017-04-17 14:20:29

    a.oneMore()函數回傳的是右值,你需要宣告一個這樣的拷貝建構子

        A(const A& x){
            printf("Copy!\n");
        }

    回覆
    0
  • PHP中文网

    PHP中文网2017-04-17 14:20:29

    另外我還發現了一個奇怪的特性, 就是我嘗試分別在oneMore和main裡面打印這個b的地址 :

        A oneMore(){
            A b("lisi");
            printf("%p\n", &b);
            return b;
        }
    int main(int argc, char* argv[]) {
        A a("zhangsan");
        A b = a.oneMore();
        printf("%p\n", &b);
    
    
        printf("1314\n");
        return 0;
    }

    結果是, 這兩個結果一樣... 也就是說他們居然指向同一塊記憶體 :

    /Users/zhangzhimin/Library/Caches/CLion2016.2/cmake/generated/geek-ef0ba4bc/ef0ba4bc/Debug/geek -wall
    zhangsanchuangjianle
    lisichuangjianle
    0x7fff52bfd9a8
    0x7fff52bfd9a8
    1314
    lisixiaohuile
    zhangsanxiaohuile
    
    Process finished with exit code 0

    按道理從oneMore裡面出來裡面所有的分配的棧上的空間都要被釋放掉的... 搞不懂- -, 難道是編譯器優化的緣故? 直接就跳過了賦值初始化, 同時不釋放那一塊在oneMore中指向b的空間, 達到初始化main裡面這個b的效果嗎?

    回覆
    0
  • 取消回覆