我正在写一些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 {
...
}
请注意,这并不一定需要保持这种状态。目标是提供一个库,其用户不需要了解其内部工作,但具有简单/干净的界面。
答案 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
}
其余代码有哪些变化?
好了,现在支持你发布的两种语法。第一种语法将移动并保存变量。第二种语法只保存对事件的引用,并假设事件的生命周期等于或大于按钮的生命周期。
此外,Click
和Hold
不需要扩展任何类,也不需要虚函数或虚拟析构函数。
如果您不希望第二种语法保留引用并使用复制,请将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
的实例