链上创建链式匿名对象

时间:2017-01-23 17:28:42

标签: c++

我正在写一些arduino库,希望提高可读性/添加一些语法糖。

我想要做的是以一种看起来像这样的方式在堆上创建对象:

Panel panel( 
    Button( 1 ).on( Click( clickfunc ) ),
    Button( 2 ).on( Hold( holdfunc, 1000 ) )
);

(Button,Click,Hold是所有类,并通过链接列表进行内部管理(所以它们不是常量。)

我尝试用这种方式编写它,但我偶然发现了对临时工具的引用问题。

目前我可以使用:

Button button1( 1 ), button2( 2 );
Click theClick( clickFunction );
Hold theHold( holdFunction, 1000 );
Panel( button1.on( theClick ), button2.on( theHold ) );

但这并不像上面那样具有可读性,并且往往容易出错,因为你必须保持警惕并且不要放置,例如点击另一个会破坏链接列表的按钮。

这些课程的摘录类似于现在。

class Button {
    Handler *_first;
    Button( int no ){...}
    Button & on( Handler &handler ){
        handler._next = _first;
        _first = &handler;
        return *this;
    }
    void handle( int oldValue, int newValue ) {
        Handler *handler;
        for( handler = _first; handler; handler = handler->_next ){
            handler->handle( oldValue, newValue );
        }
    }
}
class Handler {
    Handler *_next;
    virtual void handle( int oldValue, int newValue ) = 0;
    ...
}
class Click : public Handler {
    ...
}
class Hold : public Handler {
    ...
}

请注意,这并不一定需要保持这种状态。目标是提供一个库,其用户不需要了解其内部工作,但具有简单/干净的界面。

1 个答案:

答案 0 :(得分:1)

如果您使用上面的代码悬挂引用有问题,我怀疑您正在创建一个链接列表,该链接列表创建指向堆栈中这些元素的引用(或指针)。

我怀疑你的签名是这样的:

Button& on(const Event& event) { /* ... */ }

为了帮助您解决问题,我建议您将on功能的签名更改为以下内容:

template<typename EventType>
Button& on(EventType&& event) {

}

这样,您实际上可以将对象转发到堆中,并使用某种形式的easure将其放入链接列表中:

struct Handler {
    virtual void handle(int oldValue, int newValue) = 0;

    // Defaulted virtual destructor
    virtual ~Handler() = default;
};

template<typename T>
struct HandlerImpl : Handler {
    // constructors
    HandlerImpl(T h) : handler{std::forward<T>(h)} {}

    void handle(int oldValue, int newValue) {
        handler.handle(oldValue, newValue);
    }

    // We use the compiler generated destructor

private:
    remove_rvalue_reference_t<T> handler;
};

template<typename HandlerType>
Button& on(HandlerType&& event) {
    // See the code example below
}

其余代码有哪些变化?

好了,现在支持你发布的两种语法。第一种语法将移动并保存变量。第二种语法只保存对事件的引用,并假设事件的生命周期等于或大于按钮的生命周期。

此外,ClickHold不需要扩展任何类,也不需要虚函数或虚拟析构函数。

如果您不希望第二种语法保留引用并使用复制,请将remove_rvalue_reference_t替换为std::remove_reference_t.

我向您展示的这种模式可以应用于Button,也适用于您想要的任何小部件类型。

以下是remove_rvalue_reference_t的实施方式:

template<typename T> struct remove_rvalue_reference { using type = T; };
template<typename T> struct remove_rvalue_reference<T&&> { using type = T; };
template<typename T> using remove_rvalue_reference_t = typename remove_rvalue_reference<T>::type;

由于您已经发布了代码示例,我现在可以帮助您对其进行转换,以便它可以使用上面的代码。

首先,喜欢的列表很慢,并且手动滚动喜欢列表更糟糕。我强烈建议您使用std::vector。其次,std::unique_ptr是拥有指针的首选方式。因此,只需按照上述步骤和上述步骤操作,您的代码应如下所示:

struct Button {
    std::vector<std::unique_ptr<Handler>> _handlers;

    Button(int no) { /* ... */ }

    // This function will work for any type that
    // happen to have an `handle` function.
    template<typename H> // <--- H is the handler type
    Button& on(H&& handler) { // H&& in this case means forwarding reference.
        // We add (emplace) a new HandlerImpl, allocated on the heap using `std::make_unique`
        _handlers.emplace_back(
            std::make_unique<HandlerImpl<H>>(std::forward<H>(handler))
        );

        return *this;
    }

    void handle(int oldValue, int newValue) {
        // We use a range for loop here to iterate on the vector
        for (auto&& handler : _handlers) {
            handler->handle(oldValue, newValue);
        }
    }
};

// We do not extends anything
struct Click {
    // Notice that the function is not virtual
    void handle(int oldVal, int newVal) {/* ... */}
};

struct Hold {
    void handle(int oldVal, int newVal) {/* ... */}
};

以下是Coliru

的实例