【问题标题】:Chained anonymous object creation on the heap在堆上创建链式匿名对象
【发布时间】:2023-03-16 15:03:01
【问题描述】:

我正在编写一些 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 ) );

但这并不像上面那样可读,而且容易出错,因为你必须保持警惕,不要把例如the单击另一个按钮会破坏链接列表。

像现在这样从课程中大量缩短的摘录。

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 {
    ...
}

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

【问题讨论】:

  • 注意:堆上的对象总是匿名的,因为它们从来没有名字:它们从来都不是变量。
  • 你能把你在尝试一行时遇到的确切错误添加吗?
  • @drescherjm:也许吧。但是为什么你认为它不在堆上? (或者我在 cpp 上下文中错误地使用了“堆”这个词?))
  • @George:对此我有一个单独的问题:stackoverflow.com/questions/41811375/…。错误看起来像:“错误:没有匹配函数调用‘X::X(A)’/没有已知的参数 1 从‘A’到‘A&’的转换”
  • 实际上 Scheintod,C++ 并没有正式承认堆的存在。它只是一个动态存储池。不过,我很确定 Arduino 使用堆概念实现了动态内存。

标签: c++


【解决方案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 的实时示例

【讨论】:

  • 感谢您的回答!但是天哪(!)现在我首先必须深入挖掘我不知道的所有类型的 c++ 东西。 (但我很惭愧,我要求魔法;)
  • @Scheintod 我添加了一个更具体的示例,适用于您的代码。
  • 再次感谢。仍在尝试解决问题:) 我认为在 arduino 上我没有 std::vector。嗯。 std::unique_ptr 它也不喜欢...
  • @Scheintod 要激活 C++11(甚至更好,14),您需要告诉您的编译器使用 -std=c++14 启用它。 GCC 6 和 MSVC 在最新版本中默认启用它。 Clang 仍然需要那个标志。顺便说一句,我还添加了一个活生生的例子。如您所见,标志就在那里。
  • 酷。我印象深刻。
猜你喜欢
  • 1970-01-01
  • 2011-09-17
  • 1970-01-01
  • 1970-01-01
  • 2016-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多