2012-07-10 33 views
16

我們的C++教授提到使用operator->作爲另一個operator->的輸入的結果被認爲是糟糕的風格。爲什麼foo-> bar-> foobar被認爲是不好的風格?以及如何避免不添加代碼?

因此,而不是寫:

return edge->terminal->outgoing_edges[0]; 

他寧願:

Node* terminal = edge->terminal; 
return terminal->outgoing_edges[0]; 
  1. 爲什麼這被認爲是壞的風格?
  2. 我怎樣才能重組我的程序,以避免'壞風格',但也避免了按照上述建議創建的額外的代碼行?
+15

我從來沒有聽說過。我能看到的唯一好處是,如果你從空指針中得到一個錯誤,你可以通過行號來判斷它是否是取消引用,並且在調試器中檢查中間指針稍微容易一些。但是,如果存在空解除引用的危險,則應在第二行之前明確檢查它。不,我看不到一個很好的理由。 – Rup 2012-07-10 13:10:37

+1

調試一件事比較容易。測試終端是否爲空更簡單。 – 2012-07-10 13:10:56

+0

也許是因爲當你有一個空指針被解引用並且試圖找出哪一個時會有所幫助。這樣,當你理解錯誤行時(例如從調試器)你將知道它是哪個指針) – Mohammad 2012-07-10 13:11:03

回答

12

你應該問問你的教授,他爲什麼認爲它是不好的風格。我不。但是,我認爲他在終端聲明中忽略了const是一種壞風格。

對於像這樣的單個片段,它可能不是壞的風格。然而考慮這個問題:

void somefunc(Edge *edge) 
{ 
    if (edge->terminal->outgoing_edges.size() > 5) 
    { 
     edge->terminal->outgoing_edges.rezize(10); 
     edge->terminal->counter = 5; 
    } 
    ++edge->terminal->another_value; 
} 

這開始變得笨拙 - 這是難以閱讀,也難以寫(打字,當我做了大約10錯別字)。這需要對操作員進行大量的評估 - >對這兩個類進行評估。如果運營商是微不足道的,但如果運營商做出了令人興奮的事情,那麼最終它會做很多工作。

因此,有2個可能的答案:

  1. Maintanability
  2. 效率

和單一的片段一樣,你不能避免額外的線。在上面這樣的事情中,它會導致更少的輸入。

+6

有了這段代碼,我肯定會首先聲明指針,因爲它在同一代碼塊中被多次使用。作爲一般規則,如果我在代碼塊中多次使用某個東西,我將它聲明爲一個變量,這屬於該經驗法則。所以看來,在我的簡單例子中,這可能不是一種壞風格。但是,如果代碼更復雜,那將是。 – HorseloverFat 2012-07-10 13:44:57

+2

錯字? 「邊緣 - >終端櫃檯」 – 2012-07-10 19:29:48

+1

我休息我的情況! (儘管我發現它已經被修改來糾正它) – 2012-07-11 11:46:44

20

有很多原因。

Law of Demeter給出了一個結構性原因(請注意,您的C++教授代碼仍然違反了這一點!)。在你的例子中,edge必須知道約terminaloutgoing_edges。這使得它緊密耦合。

作爲替代

class Edge { 
private: 
    Node* terminal; 
public: 
    Edges* outgoing_edges() { 
     return terminal->outgoing_edges; 
    } 
} 

現在你可以改變的outgoing_edges實施在一個地方不改變無處不在。說實話,在數據結構如圖的情況下,我並不真正購買它(它是緊密耦合的,邊緣和節點不能互相逃脫)。這在我的書中是過分抽象的。

還有空解除引用問題,在表達式a->b->c中,如果b爲空,該怎麼辦?

+2

德米特法肯定禁止' - >'鏈,但它也禁止這個教授的首選形式'Node * terminal =邊沿>終端;'。不過,這可能是比教授理由更好的理由。 – 2012-07-10 13:44:08

+1

如果我按照我的代碼示例將「終端」更改爲Node *,這個例子是否仍然有效? (我編輯)。雖然在這個例子中,'Edge'仍然'知道'Node *和outgoing_edges [],但我不明白爲什麼它不那麼耦合。 – HorseloverFat 2012-07-10 13:57:09

+0

我認爲德米特法主要適用於可測試性。對於更復雜的對象圖,當測試(例如)邊緣時,嘲笑複雜的'terminal'對象返回一個模擬的'outgoing_edges'對象會有些惱人。只是模擬'getOutgoingEdges()'會更容易。 – 2012-07-10 14:00:38

3

您顯示的兩個代碼片段(當然)在語義上是相同的。當談到風格問題時,建議你簡單地遵循你的教授想要的東西。

略多於你的第二個代碼片段更好的方式是:

Node* terminal = (edge ? edge->terminal : NULL); 
return (terminal ? terminal->outgoing_edges[0] : NULL); 

我假設outgoing_edges是一個數組;如果不是,你必須檢查是否爲NULL或空。

6

嗯,我不確定你的教授的意思,我有一些想法。

首先,這樣的代碼可以是「不那麼明顯」:

someObjectPointer->foo()->bar()->foobar() 

你不能真的說什麼是foo()的結果,這樣的話你真的不能說上什麼是bar()被調用。這是同一個對象嗎?或者,也許它正在創建一個臨時對象?或者也許是別的?

另一件事是如果你必須重複你的代碼。考慮這樣一個例子:

complexObject.somePart.someSubObject.sections[i].doSomething(); 
complexObject.somePart.someSubObject.sections[i].doSomethingElse(); 
complexObject.somePart.someSubObject.sections[i].doSomethingAgain(); 
complexObject.somePart.someSubObject.sections[i].andAgain(); 

這樣的比較:

section * sectionPtr = complexObject.somePart.someSubObject.sections[i]; 
sectionPtr->doSomething(); 
sectionPtr->doSomethingElse(); 
sectionPtr->doSomethingAgain(); 
sectionPtr->andAgain(); 

你可以看到第二個例子是如何不僅短,而且更容易閱讀。

有時候的功能鏈更容易,因爲你並不需要它們返回什麼打擾:

resultClass res = foo()->bar()->foobar() 

VS

classA * a = foo(); 
classB * b = a->bar(); 
classC * c = b->foobar(); 

另一件事是調試。調試函數調用的長鏈非常困難,因爲您根本無法瞭解其中哪些導致錯誤。在這種情況下,打破鏈有很大幫助,更何況你可以做一些額外的檢查太像

classA * a = foo(); 
classB * b; 
classC * c; 
if(a) 
    b = a->bar(); 
if(b) 
    c = b->foobar(); 

因此,要總結,你不能說這是一個「壞」或「好「風格一般。你必須首先考慮你的情況。

1

目前的形式都不一定好。但是,如果您要在第二個示例中添加對空值的檢查,那麼這是有道理的。

if (edge != NULL) 
{ 
    Node* terminal = edge->terminal; 
    if (terminal != NULL) return terminal->outgoing_edges[0]; 
} 

雖然即使這樣仍然很糟糕,因爲誰是說,outgoing_edges正確填充或分配。更好的方法是調用一個名爲outgoingEdges()的函數,它可以爲您執行所有這些很好的錯誤檢查,並且不會導致您未定義的行爲

相關問題