2008-11-16 25 views
49

應該在構造函數中執行需要一些時間的操作,還是應該構造該對象,然後再進行初始化。構造函數應該完成多少工作?

例如,構建表示目錄結構的對象時,應該在構造函數中完成對象及其子項的填充。顯然,目錄可以包含目錄,而目錄又可以包含目錄等等。

這是什麼優雅的解決方案?

+1

構造器就像是應用程序的安裝嚮導,您只能做配置。 – 2011-05-26 12:00:57

+0

如果實例準備好對自身進行任何(可能的)操作,這意味着構造函數執行得很好。 – 2011-05-26 12:15:05

+0

嘗試這篇文章,應該是有幫助的:http://www.yegor256.com/2015/05/07/ctors-must-be-code-free.html – yegor256 2015-05-08 00:34:43

回答

3

從歷史上看,我編寫了我的構造函數,以便在構造函數方法完成後就可以使用該對象。代碼涉及多少或多少取決於對象的要求。

例如,假設我需要顯示在細節如下公司類查看:

public class Company 
{ 
    public int Company_ID { get; set; } 
    public string CompanyName { get; set; } 
    public Address MailingAddress { get; set; } 
    public Phones CompanyPhones { get; set; } 
    public Contact ContactPerson { get; set; } 
} 

因爲我要顯示我的一切有關公司的信息在詳細視圖中,我的構造函數包含填充每個屬性所需的所有代碼。鑑於這是一個複雜的類型,公司的構造函數也會觸發Address,Phones和Contact構造函數的執行。現在

,如果我填充一個目錄列表視圖,在那裏我可能只需要在公司名稱和主要電話號碼,我可以對類中的第二個構造函數只檢索信息,而空的剩餘信息,我可能只是創建一個單獨的對象,只保存該信息。這實際上取決於如何檢索信息以及從哪裏獲取信息。

無論在類的構造函數的數量的,我個人的目標是做任何處理需要準備什麼任務可在它被徵收的對象。

24

你通常不希望構造函數做任何計算。其他人使用該代碼不會期望它比基本設置更多。

對於像你所說的目錄樹,當構造對象時,「優雅」解決方案可能不會構建完整樹。相反,按要求構建它。有人使用你的對象可能並不真正關心子目錄中的內容,所以首先讓你的構造函數列表爲第一級,然後如果有人想要下載到特定的目錄中,那麼當他們請求時構建樹的那部分它。

2

至於在構造函數中應該做多少工作,我會說它應該考慮到事情的緩慢程度,你將如何使用這個類,以及你個人對它的感受。

在你的目錄結構對象上:我最近爲我的HTPC實現了一個samba(windows shares)瀏覽器,因爲這非常慢,我選擇了當它被觸摸時實際初始化一個目錄。例如首先,樹會只包含一系列機器,然後無論何時瀏覽到目錄,系統都會自動從該機器初始化樹,並將目錄列出更深的一層,依此類推。

理想情況下,我認爲你甚至可以把它寫成一個工作線程,它可以優先搜索你目前正在瀏覽的目錄,但通常這對於簡單的事情來說工作太多了;)

13

所需的時間不應該是不把某些東西放入構造函數的原因。你可以把代碼本身放到一個私有函數中,然後從你的構造函數中調用它,只是爲了保持構造函數中的代碼清晰。然而,如果你想要做的事情不需要給對象一個定義的條件,並且你可以在第一次使用時做這些事情,這將是一個合理的論點,以便將它放在後面並做。但是不要讓它依賴於你的類的用戶:這些東西(按需初始化)必須對你的類的用戶完全透明。否則,您的對象的重要不變量可能很容易中斷。

+0

Litb,我不相信你是暗示任意長的東西 - 像枚舉一個目錄樹應該進入構造函數....對吧? – Foredecker 2008-11-16 17:19:28

+2

Foredecker:這是關於類的語義:一個MutexLocker可以等待一個任意長的時間,直到資源變得可用,因爲這是構成MutexLocker的不變(析構函數被調用的時候=資源擁有)。 – 2008-11-16 17:25:02

+0

這是我可以實現的。現在,我在我的構造函數中有我想要刪除的邏輯。 – 2008-11-19 01:44:03

0

它確實取決於上下文,即類必須解決的問題。例如,它是否應該始終能夠顯示當前的孩子?如果答案是肯定的,那麼孩子不應該在構造函數中加載。另一方面,如果類表示目錄結構的快照,則它可以在構造函數中加載。

10

這取決於(典型的CS回答)。如果您在啓動時爲長時間運行的程序構建對象,那麼在構造函數中執行大量工作時沒有問題。如果這是預期快速響應的GUI的一部分,則可能不合適。與往常一樣,最好的答案是先嚐試最簡單的方法,並從中進行優化。

對於這個特定的情況,你可以做一些子目錄對象的構造。僅爲頂級目錄的名稱創建條目。如果他們被訪問,然後加載該目錄的內容。在用戶展示目錄結構時再次執行此操作。

-2

試着讓你覺得有必要,不要考慮它是否會緩慢或快速。 Preoptimization是一個時間浪費所以編碼,分析並優化它,如果需要的話。

+2

「預優化」與作爲性能缺陷的體系結構或設計選擇之間存在着巨大的差異。性能是一項功能 - 就像花哨的UI,正確性,可維護性和可靠性一樣。它的建築師和設計。 – Foredecker 2008-11-16 17:17:58

48

總結:

  • 至少,你的構造函數需要獲取配置爲它的變量是真正的點的對象。 (對象承諾隨時準備好訪問嗎?或者只有在某些狀態下才可以訪問?)一個構造函數負責所有的設置工作,前臺可能會讓班級的客戶變得更簡單。

  • 長時間運行的構造函數本質上並不壞,但在某些情況下可能不好。

  • 對於涉及用戶交互的系統,任何類型的長時間運行的方法都可能導致響應性差,應該避免。

  • 延遲計算,直到構造函數可能成爲有效優化之後;可能會變得沒有必要執行所有的工作。這取決於應用程序,不應過早確定。

  • 總的來說,這取決於。

4

爲了代碼維護,測試和調試的目的,我嘗試避免在構造函數中放置任何邏輯。如果你更願意從構造函數中執行邏輯,那麼將邏輯放入init()方法並從構造函數中調用init()會很有幫助。如果你計劃開發單元測試,你應該避免把任何邏輯放在構造函數中,因爲測試不同的情況可能會很困難。我認爲之前的評論已經解決了這個問題,但是......如果你的應用程序是交互式的,那麼你應該避免有一個單一的調用導致性能明顯下降。如果你的應用程序是非交互式的(例如:夜間批量作業),那麼單次性能下降並不是什麼大問題。

7

構造函數的最重要的工作是給對象一個初始有效狀態。在我看來,構造者最重要的期望是構造者應該沒有副作用。

1

儘可能多的,沒有更多。

構造函數必須將對象置於可用狀態,因此至少應該啓動類變量。啓發意味着什麼可以有廣泛的解釋。這是一個人爲的例子。想象一下,你有一個班級有責任提供N!到您的呼叫應用程序。

實現它的一種方法是讓構造函數不執行任何操作,並帶有一個循環的成員函數,用於計算所需和返回的值。

實現它的另一種方法是將一個類變量作爲一個數組。構造函數會將所有值設置爲-1,以表示該值尚未計算。成員函數會做懶惰評估。它看着數組元素。如果它是-1,它會計算它並存儲它並返回該值,否則它只返回數組中的值。

實現它的另一種方法就像最後一個一樣,只有構造函數會預先計算值並填充數組,所以該方法可以將值從數組中取出並返回。

實現它的另一種方法是將值保存在文本文件中,並使用N作爲文件中偏移量的基礎以從中提取值。在這種情況下,構造函數會打開文件,並且析構函數會關閉文件,而該方法會執行某種fseek/fread並返回值。

實現它的另一種方法是預先計算值並將它們存儲爲類可以引用的靜態數組。構造函數沒有任何工作,並且該方法會到達數組中以獲取該值並將其返回。多個實例將共享該數組。

所有人都說,要關注的是,通常您希望能夠調用構造函數一次,然後經常使用其他方法。如果在構造函數中做更多的工作意味着你的方法做的更少,並且運行速度更快,那麼這是一個很好的折衷。如果你正在構建/破壞很多,比如在循環中,那麼爲你的構造函數增加成本可能不是一個好主意。

2

確保ctor不做任何可能拋出異常的東西。

3

我同意長時間運行的構造函數本身並不壞。但我會認爲你幾乎總是做錯事。我的建議類似於來自Hugo,Rich和Litb的建議:

  1. 保持您在構造函數中所做的工作最少 - 保持專注於初始化狀態。
  2. 除非無法避免,否則不要從構造函數中拋出。我試圖只拋出std :: bad_alloc。
  3. 不要調用操作系統或庫API,除非你知道他們做了什麼 - 大多數可以阻止。他們會在開箱和測試機器上快速運行,但在現場,由於系統忙於做其他事情,他們可能會長時間被阻塞。
  4. 永遠不要在構造函數中做任何類型的I/O操作。 I/O通常受到各種非常長的延遲(100毫秒到幾秒)的影響。 I/O包括
    • 磁盤I/O,使用網絡(即使是間接的)印象最深的資源可以關閉箱
    • 什麼。

I/O問題,例如:許多硬盤有問題,他們進入,他們不服務讀取或寫入100的甚至上千毫秒的狀態。第一代和第一代固態硬盤經常這樣做。用戶現在已經知道你的程序問題懸而未決 - 他們只是認爲它是你的bug軟件。

當然,長時間運行的構造函數的邪惡取決於兩件事情:

  1. 什麼「長」指
  2. 該怎麼往往在一定時期內對象與「長」建設者被構造。

現在,如果'長'僅僅是幾百個額外的時鐘週期的工作,那麼它不是很長。但是一個構造函數正在進入100微秒的範圍,我認爲它很長。當然,如果你只是實例化其中的一個,或者很少實例化它們(比如說每隔幾秒),那麼由於這個範圍內的持續時間,你不可能看到問題。

頻率是一個重要的因素,一個500我們構造函數是沒有問題的,如果你只是建立一個少數人:但他們創造一百萬會造成顯著的性能問題。

讓我們來談談你的例子:填充目錄樹對象中「類目錄」對象。 (注意,我將假設這是一個帶有圖形用戶界面的程序)。在這裏,您的CTOR持續時間不依賴於您編寫的代碼 - 它的被告在枚舉任意大的目錄樹所花費的時間。這在本地硬盤上已經夠糟糕了。它在遠程(網絡)恢復上更成問題。

現在,想象你的用戶界面線程這樣做 - 你的UI將在其軌道上停死了秒,秒或潛在的甚至10分鐘的。在Windows中,我們稱之爲UI掛起。他們壞壞壞(是的,我們有他們...是的,我們努​​力消除他們)。

用戶界面掛起會讓人們真正討厭你的軟件。

這裏要做的正確的事情就是簡單地初始化你的目錄對象。建立你的目錄樹中是可以被取消,並保持在響應狀態的UI循環(取消按鈕應始終工作)的對象總是會使用默認的(無參數)構造函數的

-2

陣列。這是沒有辦法的。

有「特殊」構造函數:複製構造函數和運算符=()。

你可以有很多構造函數!或者稍後結束很多構造函數。比利時拉比地區需要一個帶浮動的新構造函數,而不是雙倍存儲這4個糟糕的字節。 (買一些RAM比爾!)

不能調用構造函數一樣可以普通的方法來重新調用該初始化邏輯。

您不能使構造函數邏輯虛擬化,並在一個子類中進行更改。 (但如果你是從構造函數調用的initialize()方法,而不是手動,虛擬方法是行不通的。)

當構造函數中存在重要的邏輯時,所有這些事情都會產生很多悲傷。 (或至少重複的代碼。)

所以我作爲一個設計選擇,喜歡有最小的構造函數(可選地,根據它們的參數和情況)調用一個initialize()方法。

根據具體情況,initialize()可能是私有的。或者它可能是公開的並且支持多個調用(例如,重新初始化)。

最終,這裏的選擇因情況而異。我們必須保持靈活性,並考慮權衡。沒有一個適合所有人的方式。

的方法,我們會使用來實現與同時使用線程交談一塊專用的硬件,並且已被寫入在1/2小時也不一定就是我們會使用單孤實例類實現一個代表數學的數學課程,內容涉及數個月內編寫的浮點精度浮點數。

1

如果在構造函數之外可以做某件事,請避免在裏面做。後來,當你知道你的班級以其他方式表現良好時,你可能會冒險做到這一點。

1

RAII是C++資源管理的骨幹,所以獲得你在構造函數中需要的資源,在析構函數釋放他們。

這是當你建立你的類不變量。如果需要時間,則需要時間。 「如果X存在做Y」構造的數量越少,則其餘的課程設計就越簡單。之後,如果性能分析顯示這是一個問題,請考慮惰性初始化(首次需要時獲取資源)等優化。

0

我投了薄的構造,而在這種情況下,增加一個額外的「未初始化」狀態行爲的對象。

原因是:如果你不這樣做,你強加所有用戶要麼有大的構造太,或者動態地分配你的類。在這兩種情況下,它可能被視爲一件麻煩事。

可能很難趕上從這樣的對象錯誤,如果他們成爲靜態的,因爲構造則之前的主運行(),更困難的調試跟蹤。

0

很好的問題:其中一個「目錄」對象有與其他「目錄」對象的引用,你給的例子也是一個很好的例子。

對於這個特定的情況,我會移動代碼以從構造函數中構建下級對象(或者可以在這裏推薦的另一篇文章中做第一級[直接子]],並且有一個單獨的「初始化」或「構建'機制)。

另外還有一個潛在的問題 - 不僅僅是性能 - 這是內存佔用:如果你最終做出非常深的遞歸調用,你最終可能也會遇到內存問題[因爲堆棧會保留所有的局部變量直到遞歸結束]。