2013-12-16 109 views
1

好吧,也許我的問題是一點點conveved。C++ 11/lambda函數和函數指針

我想找到一個優雅的解決方案,以減少以下樣板代碼,每次我想修改一個對象的一部分,只能通過一個const Getter和一個非const設置器訪問。

Content c = container.GetContent(); 
c.SetX(3); 
container.SetContent(c); 

我知道我可以有一個非const的getter,但我想暫時堅持下去。

所以,我試圖用lambda表達式,我目前有以下實現:

#include <iostream> 

class Content 
{ 
public: 
    Content(int x) :mX(x) {} 
    const int GetX() const 
    { 
     return mX; 
    } 
    void SetX(const int &x) 
    { 
     mX = x; 
    } 
private: 
    int mX; 
}; 


//for clarity ContentFunctionChanger is a typedef for any function of type : void f(Content &) 
typedef void (*ContentFunctionChanger)(Content &); 


class Container 
{ 
public: 
    Container(const Content &c) :mContent(c) {} 
    const Content & GetContent() const 
    { 
     return mContent; 
    } 
    void SetContent(const Content &c) 
    { 
     mContent = c; 
    } 

    void ChangeContent(ContentFunctionChanger &function) 
    { 
     (*function)(mContent); 
    } 

private: 
    Content mContent; 
}; 


int main() 
{ 
    Content content(1); 
    Container container(content); 

    std::cout << "x=" << container.GetContent().GetX() << std::endl; 

    { 
     //Classic method using Get() then Set() 
     Content c = container.GetContent(); 
     c.SetX(3); 
     container.SetContent(c); 
     std::cout << "x=" << container.GetContent().GetX() << std::endl; 
    } 

    { 
     //Method 1 : with a named lambda function whose type is written at the declaration 
     //It works, but it is not concise 
     ContentFunctionChanger func = [] (Content & c) { c.SetX(5); }; 
     container.ChangeContent(func); 
     std::cout << "x=" << container.GetContent().GetX() << std::endl; 
    } 
    /* 
    { 
     //Method 2 : with a named lambda function whose type is not written (using auto) 
     //It will not compile... 
     auto func = [] (Content & c) { c.SetX(7); }; 
     container.ChangeContent(func); 
     std::cout << "x=" << container.GetContent().GetX() << std::endl; 
    } 

    { 
     //Method 3: with an anonmymous lambda. 
     //Concise enough, but it does not compile either... 
     container.ChangeContent([] (Content & c) { c.SetX(9); }); 
     std::cout << "x=" << container.GetContent().GetX() << std::endl; 
    } 
    */ 

    return 0; 
} 

我的問題是方法2和3是更簡潔,但他們不會編譯。 我不知道是否有希望讓他們編譯。

任何人都可以幫忙嗎?

回答

2

您可以使用模板來實現方法3:

template<typename F> 
void ChangeContent(F function) 
{ 
    function(mContent); 
} 

這將允許你通過什麼調用(仿函數,例如)。

另一個(C++ 03)的方法是實現流暢界面Set方法:

// kind of a functional set — if we want Set to constant, we need to return a new object 
Content SetX(const int &x) const 
{ 
    Content ret = *this; 
    ret.mX = x; 
    return ret; 
} 

並以此爲如下:

{ 
    //Fluent interface 
    container.SetContent(container.GetContent().SetX(111)); 
    std::cout << "x=" << container.GetContent().GetX() << std::endl; 
} 
+0

@woolstar本地對象永遠不會被引用返回。在這種情況下'this'是const的,所以我們不能修改它返回'* this'。 –

+0

我喜歡你的解決方案,因爲我可以避免使用函數指針的錯綜複雜的語法來搔癢我的頭。 –

3

你的問題是,你試圖通過一個臨時對象作爲參考:從本聲明刪除&,它會工作:

void ChangeContent(ContentFunctionChanger &function) 

(當然,你還需要重命名func2func在一個地方)。無論如何,通過引用傳遞一個函數指針實際上沒有任何意義。這樣做只是增加了另一種間接方式,沒有任何好處,不必要的間接性往往只是花費時間。

只是爲了解釋臨時來自哪裏:lambda表達式的類型是每個lambda表達式的唯一類型。如果lambda函數有一個空的捕獲,它可以被轉換爲一個函數指針。在你的第一個代碼中,你顯式地做了這個轉換,產生一個可以綁定到引用的左值。在另外兩種情況下,您依賴於隱式轉換,該轉換產生的右值不能與非const引用相綁定(即,您可以通過在&前添加const來解決問題,但額外的間接方式是仍然毫無意義)。

+0

謝謝,我應該更好地閱讀編譯器輸出! –

1

在一個完全不同的方向上行進,有SetX返回對象的引用,然後您可以鏈接您的參數:

Content & SetX(const int &x) 
    { 
     mX = x; 
     return * this ; 
    } 

... 

container.SetContent(container.GetContent().setX(3)) ; 

然而,在這種特殊情況下,container.GetContent()返回const,所以您甚至無法調用setX,因此如果必須創建一個新對象來修改它,爲什麼還要打擾調用GetContent()

雖然其他人已修改setX的行爲返回一個新的對象,我認爲這不適合動詞。我期望set*修改一個對象,而不是返回一個新的。所以這裏是我如何解決保留setX的含義並處理來自getter的const值的問題。

在您不是從Content複製的東西簡單的例子,只是做一個新問題:

container.SetContent(Content(3)) ; 

或者在更復雜的地方有一些有價值的狀態,扔在一個臨時對象:

container.SetContent(Content(container.getContent()).setX(3)) ; 

謝天謝地,我認爲getters/setters的趨勢正在下降。

+0

也不錯! –

+0

...但它沒有編譯(如woolstar在他的評論中解釋)。但我確實喜歡鏈接的原始概念 –

+0

解決了'getContent()'的'const'問題。我不喜歡'setX'返回一個新對象。 – woolstar

0

以下是使用Barmaley.exe建議更正的代碼。

我們正在接近具有類似於C#性質的行爲,因爲像

container.ChangeContent([] (Content & c) { c.SetX(9); }); 

一個呼叫可以調用一個非平凡設定器(即例如將改變對象修改日期)

全代碼如下:

#include <iostream> 

class Content 
{ 
public: 
    Content(int x) :mX(x) {} 
    const int GetX() const 
    { 
     return mX; 
    } 
    void SetX(const int &x) 
    { 
     mX = x; 
    } 
private: 
    int mX; 
}; 

class Container 
{ 
public: 
    Container(const Content &c) :mContent(c), mWasUpdated(false) {} 
    const Content & GetContent() const 
    { 
     return mContent; 
    } 
    void SetContent(const Content &c) 
    { 
     mContent = c; 
     mWasUpdated = true; //dummy example of a non trivial setter 
    } 
    bool WasUpdated() const 
    { 
     return mWasUpdated; 
    } 

    //FnContentChanger can be a function, a functor, any callable that modifies Content... 
    //We are getting closer to having a behaviour that resemble C# properties 
    template<typename FnContentChanger> 
    void ChangeContent(FnContentChanger function) 
    { 
     Content c = GetContent(); 
     function(c); 
     SetContent(c); 
    } 

private: 
    bool mWasUpdated; 
    Content mContent; 
}; 


int main() 
{ 
    { 
     //Classic method using Get() then Set() 
     Content content(1); 
     Container container(content); 

     std::cout << "x=" << container.GetContent().GetX() << " wasUpdated=" << container.WasUpdated() << std::endl; 
     Content c = container.GetContent(); 
     c.SetX(3); 
     container.SetContent(c); 
     std::cout << "x=" << container.GetContent().GetX() << " wasUpdated=" << container.WasUpdated() << std::endl; 
    } 

    { 
     //Method 2: with an anonmymous lambda. 
     Content content(1); 
     Container container(content); 

     std::cout << "x=" << container.GetContent().GetX() << " wasUpdated=" << container.WasUpdated() << std::endl; 
     container.ChangeContent([] (Content & c) { c.SetX(9); }); 
     std::cout << "x=" << container.GetContent().GetX() << " wasUpdated=" << container.WasUpdated() << std::endl;  
    } 
    return 0; 
}