簡體   English   中英

對象已在聲明中初始化?

[英]Object is already initialized on declaration?

我試圖用C ++來理解一些東西。 基本上我有這個:

class SomeClass {
    public:
        SomeClass();
    private:
        int x;
};

SomeClass::SomeClass(){
    x = 10;
}

int main() {
    SomeClass sc;
    return 0;
}

我認為sc是SomeClass類型的未初始化變量,但是從各種教程我發現看起來這個聲明實際上是一個調用SomeClass()構造函數的初始化,而我不需要調用“sc = new SomeClass();” 或類似的東西。

當我來自C#世界(並且知道一點C,但沒有C ++)時,我試圖理解何時需要像new這樣的東西以及什么時候發布這樣的對象。 我發現了一種名為RAll的模式似乎與此無關。

什么是這種類型的初始化調用,我怎么知道某些東西是僅僅是聲明還是完全初始化?

我想這里有幾件事:

  • 自動變量和動態分配變量之間的區別
  • 物體的壽命
  • RAII
  • C#並行

自動與動態

自動變量是系統將管理生命周期的變量。 讓我們暫時拋棄全局變量,它很復雜,並專注於通常情況:

int main(int argc, char* argv[])  // 1
{                                 // 2
  SomeClass sc;                   // 3
  sc.foo();                       // 4
  return 0;                       // 5
}                                 // 6

這里sc是一個自動變量。 在執行第(3)行成功完成后,保證完全初始化(即構造函數保證已運行)。 它的析構函數將在第(6)行自動調用。

我們一般都談到變量的范圍:從聲明的角度到相應的結束括號; 當范圍退出時,語言保證破壞,無論是return還是例外。

當然,如果您調用可怕的“未定義行為”(通常會導致崩潰),則無法保證。

另一方面,C ++也有動態變量,即使用new分配的變量。

int main(int argc, char* argv[])  // 1
{                                 // 2
  SomeClass* sc = 0;              // 3
  sc = new SomeClass();           // 4
  sc->foo();                      // 5
  return 0;                       // 6
}                                 // 7 (!! leak)

這里sc仍然是一個自動變量,但它的類型不同:它現在是一個指向SomeClass類型變量的指針。

在第(3)行, sc被賦予一個空指針值(C ++ 0x中的nullptr ),因為它沒有指向SomeClass任何實例。 請注意,該語言不保證自己進行任何初始化,因此您需要明確指定一些內容,否則您將擁有垃圾值。

在第(4)行,我們構建一個動態變量(使用new運算符)並將其地址分配給sc 請注意,動態變量本身是未命名的,系統只給我們一個指針(地址)。

在第(7)行,系統自動銷毀sc ,但它不會破壞它所指向的動態變量,因此我們現在有一個動態變量,其地址不存儲在任何地方。 除非我們使用垃圾收集器(在標准C ++中不是這種情況),否則我們泄漏了內存,因為變量的內存在進程結束之前不會被回收...甚至那時析構函數也不會被運行(如果它有副作用太糟糕了)。

物體的生命周期

Herb Sutter有一篇關於這個主題的非常有趣的文章。 這是第一個

作為總結:

  • 只要構造函數運行完成,對象就會存在。 這意味着如果構造函數拋出,對象永遠不會存在(認為它是懷孕的意外)。
  • 一旦對象被調用它就會死掉,如果析構函數拋出(這是EVIL)它就不能再次嘗試,因為你不能在死對象上調用任何方法,它是未定義的行為。

如果我們回到第一個例子:

int main(int argc, char* argv[])  // 1
{                                 // 2
  SomeClass sc;                   // 3
  sc.foo();                       // 4
  return 0;                       // 5
}                                 // 6

sc從第(4)行到第(5)行(包括第5行)存活。 在第(3)行,它正在構建(可能由於多種原因而失敗),在第(6)行它正在被破壞。

RAII

RAII意味着資源獲取是初始化 這是管理資源的慣用語,特別是確保資源一旦獲得就最終會被釋放。

在C ++中,由於我們沒有垃圾收集,這個習慣用法主要應用於內存管理,但它對任何其他類型的資源也很有用:多線程環境中的鎖,文件鎖,網絡中的套接字/連接等等......

當用於內存管理時,它用於將動態變量的生命周期與給定的一組自動變量的生命周期相結合,確保動態變量不會比它們更長(並且丟失)。

在最簡單的形式中,它耦合到一個自動變量:

int main(int argc, char* argv[])
{
  std::unique_ptr<SomeClass> sc = new SomeClass();
  sc->foo();
  return 0;
}

它與第一個示例非常相似,只是我動態分配了SomeClass一個實例。 然后將此實例的地址傳遞給sc對象,類型為std::unique_ptr<SomeClass> (它是一個C ++ 0x工具,如果不可用則使用boost::scoped_ptr )。 unique_ptr保證當sc被銷毀時,指向的對象將被銷毀。

在一個更復雜的形式中,它可能使用(例如) std::shared_ptr耦合到幾個自動變量,顧名思義它允許共享一個對象並保證在銷毀最后一個共享器時該對象將被銷毀。 請注意,這不等同於使用垃圾收集器,並且可能存在引用循環的問題,我不會深入此處,所以請記住,而不是std::shared_ptr不是靈丹妙葯。

因為面對異常和多線程代碼,在沒有RAII的情況下完美地管理動態變量的生命周期非常復雜,建議如下:

  • 盡可能使用自動變量
  • 對於動態變量,永遠不要自己調用delete並始終使用RAII工具

我個人認為任何delete都是強烈可疑的,我總是要求在代碼審查中將其刪除:這是一種代碼味道。

C#並行

在C#中,您主要使用動態變量* 這就是為什么:

  • 如果你只是聲明一個沒有賦值的變量,它的值是null:實質上你只是操作指針,因此你有一個空指針(保證初始化,謝天謝地)
  • 您使用new來創建值,這會調用對象的構造函數並生成對象的地址; 請注意語法與動態變量的C ++類似

但是,與C ++不同,C#是垃圾收集的,因此您不必擔心內存管理。

收集垃圾也意味着對象的生命周期更難以理解:它們是在您要求它們時構建的,但在系統方便時被破壞。 這可能是實現RAII的問題,例如,如果您真的希望快速釋放鎖,並且該語言有許多工具可以幫助您從內存中using關鍵字+ IDisposable接口。

* :它很容易檢查,如果在聲明變量后它的值為null ,那么它將是一個動態變量。 我相信對於int該值將為0表示它不是,但是自從我為一個課程項目擺弄C#以來已經有3年了...

你在main()的第一行做的是在堆棧上分配一個SomeClass對象。 new運算符改為在堆上分配對象,返回指向類實例的指針 這最終導致了兩種不同的訪問技術. (使用實例)或使用-> (使用指針)

既然你知道C,那么每次你說時你都會執行堆棧分配,例如int i; 另一方面,堆分配在C中使用malloc()執行。 malloc()返回一個指向新分配空間的指針,然后將其轉換為指向某個東西的指針。 例:

int *i;
i = (int *)malloc(sizeof(int));
*i=5;

雖然在堆棧上分配的東西的釋放是自動完成的,但是在堆上分配的東西的解除分配必須由程序員完成。

您混淆的原因來自於C#(我不使用,但我知道它與Java類似)沒有堆棧分配。 當你說SomeClass sc ,你所做的就是聲明一個SomeClass引用,它當前是未初始化的,直到你說new ,這是對象彈出的時刻。 new之前,你沒有對象。 在C ++中,情況並非如此。 C ++中沒有類似於C#(或java)的引用概念,盡管在函數調用期間只有C ++中的引用(在實踐中它是一個傳遞引用的范例。默認情況下,C ++按值傳遞,意味着你在函數調用時復制對象)。 然而,這不是全部。 查看評論以獲取更准確的詳細信息。

在您的情況下,使用SomeClass的默認構造函數在堆棧上分配sc 由於它在堆棧上,因此從函數返回時將破壞實例。 (如果你在一個從main調用的函數中實例化SomeClass sc將會更令人印象深刻 - 分配給sc內存在返回main時將被取消分配。)

new關鍵字不是在運行時堆棧上分配內存,而是在堆上分配內存。 由於C ++沒有自動垃圾收集,因此您(程序員)負責取消分配您在堆上分配的任何內存(使用delete關鍵字),以避免內存泄漏。

當您在函數范圍(例如main )中聲明變量(不帶extern )時,您還定義了變量。 該變量在到達聲明時出現,並在其范圍結束時(在本例中為函數main的結尾)到達時不存在。

當一個對象存在時,如果它有一個用戶聲明的構造函數,那么它的一個構造函數用於初始化它。 類似地,如果它具有用戶聲明的析構函數,則當對象超出范圍以在其超出范圍時執行任何所需的清理操作時使用此方法 這與具有可能會或可能不會運行的終結器的語言不同,當然也不是在確定的時間點。 它更像是using / IDisposable

在C ++中使用new表達式來動態創建對象。 它通常用於對象的生命周期不能綁定到特定范圍的地方。 例如,在創建它的函數完成后它必須繼續存在。 它也用於現在在編譯器時知道要創建的對象的確切類型的地方,例如在工廠函數中。 在許多通常用於Java和C#等語言的情況下,通常可以避免動態創建對象。

使用new創建對象時,必須在某個時刻通過delete表達式銷毀它。 為了確保程序員不要忘記這樣做,通常使用某種智能指針對象來自動管理它,例如來自tr1或boost的shared_ptr

其他一些答案基本上告訴你“sc在堆棧上分配,new在堆上分配對象”。 我不想以這種方式考慮它,因為它將實現細節(堆棧/堆)與代碼的語義混為一談。 既然你習慣了C#做事的方式,我認為它也會產生歧義。 相反,我更喜歡考慮它的方式是C ++標准描述它的方式:

sc是SomeClass類型的變量,在塊作用域中聲明(即構成主函數的大括號)。 這稱為局部變量 因為它沒有聲明為staticextern ,所以這使它具有自動存儲持續時間 這意味着每當SomeClass sc;SomeClass sc; 執行時,變量將被初始化(通過運行其構造函數),當變量超出范圍時退出塊,它將被銷毀(通過運行它的析構函數 - 因為你沒有它,你的對象很簡單數據,什么都不會做)。

之前我曾說過“因為它沒有被聲明為staticextern ”,如果你已經聲明它,那么它將具有靜態存儲持續時間 它將在程序啟動之前初始化(技術上在塊范圍,它將在第一次使用時初始化),並在程序終止后銷毀。

使用new創建對象時,可以創建具有動態存儲持續時間的對象。 當您調用new ,將初始化此對象,並且只有在您調用delete時才會銷毀該對象。 要調用delete,您需要維護對它的引用,並在完成對象使用后調用delete。 編寫良好的C ++代碼通常不會非常使用這種類型的存儲持續時間,而是通常將值對象放入容器(例如std::vector )中,這些容器管理包含值的生命周期。 容器變量本身可以進入靜態存儲或自動存儲。

希望這有助於消除歧義,而不會濫用太多新術語來混淆你。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM