2013-12-16 110 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; 
}