c++ - 在堆上创建链式匿名对象

标签 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 ) );

但这并不像上面那样可读,而且容易出错,因为你必须保持警惕并且不要把例如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
}

您的其余代码有何变化?

好吧,现在您发布的两种语法都受支持。第一个语法将移动并保持变量。第二种语法将仅保留对事件的引用,并假定事件的生命周期等于或大于按钮的生命周期。

此外,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 上的一个实例

关于c++ - 在堆上创建链式匿名对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41812150/

相关文章:

c++ - 使用数据类型(类类型)作为映射中的键

.net - 找不到相关程序集 Microsoft.VC90.CRT 错误

c++ - 如何创建 std::tuple 的别名?

c++ - : Handle, 指针和引用有什么区别

c++ - 编译代码太快会出错

c++ - 数据拼接时输出成员变量c++

c++ - 如何从装饰名称中取消装饰名称?

c++ - ArrayFire CUDA 应用程序在第一分钟极其缓慢

c++ - 静态库&动态库——更多C++乐趣

C++ : WordCount from Char string with pointers. ...?