我正在写一些 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 ) );
但这并不像上面那样可读,而且容易出错,因为你必须保持警惕并且不要把例如theClick on another button which would break the linked list.
像现在这样的类(class)的一些大幅缩短的摘录。
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 {
...
}
请注意,这不一定需要保持这种方式。目标是提供一个用户不需要了解其内部工作的太多信息但具有简单/干净界面的库。
最佳答案
如果您对上述代码的悬空引用有疑问,我怀疑您正在创建一个链表,该链表创建指向堆栈中那些元素的引用(或指针)。
我还怀疑你的签名是这样的:
Button& on(const Event& event) { /* ... */ }
为了帮助您解决问题,我建议将您的 on
函数的签名更改为如下所示:
template<typename EventType>
Button& on(EventType&& event) {
}
这样,您实际上可以将对象转发到堆中,并使用某种形式的类型保证将其放入您的链表中:
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 上的一个实例
关于c++ - 在堆上创建链式匿名对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41812150/