2011-09-23 102 views
13

這可能是一個哲學問題,但我遇到了以下問題:爲什麼std :: function實例有一個默認的構造函數?

如果你定義一個std ::功能,你不正確初始化它,你的應用程序會崩潰,就像這樣:

typedef std::function<void(void)> MyFunctionType; 
MyFunctionType myFunction; 
myFunction(); 

如果函數作爲參數傳遞,就像這樣:

void DoSomething (MyFunctionType myFunction) 
    { 
    myFunction(); 
    } 

然後,當然,它也崩潰。這意味着,我不得不像下面這樣添加校驗碼:

void DoSomething (MyFunctionType myFunction) 
    { 
    if (!myFunction) return; 
    myFunction(); 
    } 

要求這些支票給了我一個閃回舊的C日子裏,在那裏你也必須明確地檢查所有指針參數:

void DoSomething (Car *car, Person *person) 
    { 
    if (!car) return;  // In real applications, this would be an assert of course 
    if (!person) return; // In real applications, this would be an assert of course 
    ... 
    } 

幸運的是,我們可以在C++,它阻止我寫這些檢查(假設呼叫者沒有一個nullptr的內容傳遞給函數使用引用:

void DoSomething (Car &car, Person &person) 
    { 
    // I can assume that car and person are valid 
    } 

所以,WH做std ::函數實例有一個默認的構造函數?沒有默認的構造函數,你就不需要添加檢查,就像函數的其他普通參數一樣。 在那些你想傳遞'可選'std :: function的'罕見'情況下,你仍然可以傳遞一個指針給它(或者使用boost :: optional)。

+4

它不會崩潰;它會拋出一個異常。 –

+0

閱讀關於'functors':http://www.sgi.com/tech/stl/functors.html –

+0

「我不得不添加檢查代碼」 - 恥辱,你的呼叫者不能修復他們的代碼。似乎很奇怪,爲了挽救他們需要處理異常而放棄。儘管如此,它可能更糟糕,如果你使用了一個函數指針,並且他們不打算初始化它,那麼它會有一個不確定的值,行爲將是未定義的。他們可能把調用'strlen'的方法糟糕得多,而不是調用你的函數。 –

回答

14

沒錯,但是這也適用於其他類型。例如。如果我想讓我的類有一個可選的Person,那麼我讓我的數據成員成爲一個Person指針。爲什麼不爲std :: functions做同樣的事情? std :: function有什麼特別的地方,它可以有一個'無效'狀態?

它沒有「無效」狀態。這是沒有比這更無效:

std::vector<int> aVector; 
aVector[0] = 5; 

你有什麼是function,就像aVector是一個空vector。該對象處於非常明確的狀態:沒有數據的狀態。

現在,讓我們看看你的「函數指針」建議:

void CallbackRegistrar(..., std::function<void()> *pFunc); 

你怎麼還打電話嗎?那麼,這裏的一件事你不能做:

void CallbackFunc(); 
CallbackRegistrar(..., CallbackFunc); 

這是不允許的,因爲CallbackFunc是一個函數,而參數類型是std::function<void()>*。這兩個不是可轉換的,所以編譯器會抱怨。所以爲了打電話,你必須這樣做:

void CallbackFunc(); 
CallbackRegistrar(..., new std::function<void()>(CallbackFunc)); 

你剛剛在圖片中介紹了new。您已分配資源;誰會對此負責? CallbackRegistrar?很明顯,你可能想使用某種智能指針,讓你的雜亂界面更加有:

void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc); 

這是一個很大的API煩惱和克魯夫特,僅僅是傳遞函數。避免這種最簡單的方法是允許std::function爲空。就像我們允許std::vector爲空。就像我們允許std::string爲空。就像我們允許std::shared_ptr爲空。等等。

簡單說就是:std::function包含的一個函數。它是可調用類型的持有者。因此,它可能不包含可調用類型。

+2

顯然,你需要'boost :: optional MSalters

+3

@ MSalters:是的,那可行。但令人遺憾的是'optional'不是C++標準庫的一部分。對於那些不允許使用Boost的可憐的,不幸的靈魂來說,這根本就不是一種選擇。 –

+0

投票,好的措辭:可選...這根本不是一個選項,哈哈! –

5

其中最常見的用例std::function是註冊回調,在滿足一定條件下被調用。允許未初始化的實例可以僅在需要時註冊回調,否則將被迫始終傳遞至少某種無操作函數。

+0

是的,但對其他類型也是如此。例如。如果我想讓我的類有一個可選的Person,那麼我讓我的數據成員成爲一個Person指針。爲什麼不爲std :: functions做同樣的事情? std :: function有什麼特別的地方,它可以有一個'無效'狀態? – Patrick

+4

在我看來,你應該將std :: function看作是可以調用的一種智能指針。你不會期望使用指向shared_ptr的指針,對嗎? –

1

有,你不能在初始化建設一切情況下(例如,當一個參數取決於另一個施工的影響,反過來依賴於對第一......)。

在這種情況下,你有必要打破循環,承認了身份無效狀態稍後修正。 因此,您將第一個構造爲「null」,構造第二個元素,並重新分配第一個元素。

可以,其實,避免檢查,如果-where一個功能used-,你同意嵌入它的對象的構造函數中,你總是會後有效調動返回。

+0

我不認爲有什麼方法可以「稍後糾正」空的std ::函數。如果在創建時它是空的,它總是空的。 (當然,不管你認爲它是無效狀態,是另一回事。) – max

+0

@max對我來說看起來像一個「相同的概念 - 不同的措辭」問題。 –

+0

我想我當時並不理解你的論點。 你說你可以使用一個空的'std :: function',當你不知道你想要的值是什麼時(直到後來你不能在ctor中初始化它)。我同意這將是一個很好的用例。但是我沒有看到的是,你以後會做什麼 - 當你最終*知道你想要的價值時。這不像你可以將一個空的'std :: function'改成其他任何東西? – max

5

答案可能是歷史的:std::function意味着作爲函數指針的替代,函數指針的能力是NULL。所以,當你想提供與函數指針的簡單兼容性時,你需要提供一個無效的狀態。

的身份無效狀態是不是因爲真的有必要,正如你所說,boost::optional做這項工作就好了。所以我會說,std::function只是爲了歷史的緣故。

9

其實,你的應用程序應該不會崩潰。

§20.8.11.1 class bad_function_call [func.wrap。badcall]

1/bad_function_call一種類型的異常是由function::operator()(20.8.11.2.4)當函數包裝對象沒有目標拋出。

該行爲是完全明確的。

+2

它可能被指定,但規範還說,在發生異常情況下,程序終止(即:崩潰)。如果你不抓住它,會發生什麼。 –

+2

@NicolBolas:你說得對,但我不認爲這是一個崩潰,因爲許多操作可能會引發一個異常:將一些東西分配給'std :: function'可能會執行內存分配。因此,不處理異常與'std :: function'可能不會用回調初始化這一事實完全無關。 –

0

以同樣的方式,你可以添加一個nullstate到一個沒有的函數類型,你可以用一個不包含nullstate的類包裝一個函子。前者需要添加狀態,後者不需要新狀態(僅限制)。因此,雖然我不知道std::function設計的基本原理,但無論您想要什麼,它都支持最精簡的平均使用率。

乾杯&心連心,

0

你只使用std ::回調函數,你可以使用其參數轉發給處理一個簡單的模板輔助函數,如果它不爲空:

template <typename Callback, typename... Ts> 
void SendNotification(const Callback & callback, Ts&&... vs) 
{ 
    if (callback) 
    { 
     callback(std::forward<Ts>(vs)...); 
    } 
} 

而在下面的方式來使用它:

std::function<void(int, double>> myHandler; 
... 
SendNotification(myHandler, 42, 3.15); 
相關問題