将不同的函数存储在一个函数指针下

Storing different functions under one function pointer

提问人:Default 提问时间:9/28/2023 最后编辑:Default 更新时间:9/28/2023 访问量:79

问:

请允许我为我的问题提供一些背景信息:


我有一个大学作业,为我提供了一个名为 StringList 的类。这个类负责很多字符串操作,如插入删除等。这些方法的函数签名的一些示例如下所示:


// Must be undoable
void insert_before(int index, const std::string &s)
{
    //...
}

// Must be undoable
void remove_bofore(int index)
{
    //...
}

void undo()
{
    // Attempts to revert back to the last operation
}

该任务要求我实现一个堆栈,该堆栈能够提供 undo() 操作,该操作基本上可以反转之前发生的功能。例如,如果某些代码决定调用名为 insert_before() 的类方法,则 StringList 类必须使用堆栈来提供通过删除插入的字符串操作来恢复的功能。

实现堆栈似乎是一项相当微不足道的任务,但主要问题是试图弄清楚如何以最简单的方式实现 undo() 操作。我最初的想法是创建一个 Node 结构,除了允许它引用其他节点的标准下一个指针之外,还将包含一个函数指针变量,如下所示:


// Used in singly linked list stack
struct Node
{
  Node* next;
  
  void(*function)();
};

这有助于存储需要调用的所需函数,以便撤消()之前执行的操作。这是通过保存上次执行的完全相反的操作来完成的。例如,如果调用 insert_before() 方法,则其行为如下:


void insert_before(int index, const std::string &s)
{
    // Perform the insertion...
    
    stack.push(
        remove_before(index)    // Storing the function that needs to be called by undo()...
    );
}

我在这种方法中面临的主要问题是,我不确定是否允许我们在一个通用函数指针下存储不同的函数签名。函数指针显然需要一个函数,该函数的返回类型签名为 void,参数为 void 类型。但是,需要存储在其中的每个函数都有不同的签名,而这些只是 2 种方法,还有更多。


我可以继续为每个方法类型创建一个函数指针,但我认为为每个方法类型创建这么多不同的函数指针不是一个好主意。


那么,我该怎么做呢?我的意思是。。。是否可以将它们全部存储在一个函数指针下?或者我还能用它做些什么吗?

C++ 字符串 函数 指针 堆栈

评论

1赞 paddy 9/28/2023
为此,您可能需要考虑使用 lambda,因为它们可以捕获可能很重要的额外绑定(例如,执行操作的索引)。lambda 可以做任何你想做的事,但提供一致的签名(例如 std::function<void (UndoContext&)>)
2赞 Pepijn Kramer 9/28/2023
你不需要实现自己的堆栈,你可以使用 std::stack。将此与@paddy的评论相结合,您可以执行以下操作:std::stack<std::function<void()>>stack.push([=]{ remove_before(index); })
0赞 Default 9/28/2023
不幸的是,我不能使用 ,因为我需要手动实现自定义。std::stack

答:

2赞 HolyBlackCat 9/28/2023 #1

您不需要不同的签名。签名将始终是 ,或类似的东西。 不带任何参数,因此需要撤消的任何其他参数都需要烘焙到要存储的函数中。void(StringList &)undo()

使用捕获 lambda:

stack.push([index](StringList &list)
{
    list.remove_before(index);
});

由于函数指针不能指向捕获 lambda,因此请改用。std::function

评论

0赞 Default 9/28/2023
我不被允许使用.对不起;)std::function
0赞 Default 9/28/2023
不过,还有其他方法。其中一些具有不同的返回类型以及不同数量的参数和类型。这个解决方案是否仍然对他们有用?
0赞 HolyBlackCat 9/28/2023
@Default 请列出问题中的所有限制。我不想使用不同的功能重写它,却被告知它也是不允许的。
0赞 Default 9/28/2023
应该如何更改我的 Node 类,以便接收具有不同签名和可变数量的参数和类型的 lambda?
1赞 HolyBlackCat 9/28/2023
@Default 该签名不起作用。你被迫使用那个吗?它需要(但由于函数指针不能指向捕获 lambda,因此这需要是 instead 的签名或虚拟函数的签名)。void (*function)(StringList &)std::function
0赞 billz 9/28/2023 #2

将不同的函数签名存储在一个通用函数指针下

会使解决方案变得复杂。 你听说过命令设计模式吗? 当调用insert_before或删除函数时,生成命令并将命令缓存在动作向量中。 因此,当调用 Undo 时,只需pop_back向量并执行它即可。 示例代码如下:

struct StringList
{
    std::vector<Action> actions;
    
    void insert_before(int index, const std::string &s)
    {
        //...
        
        collections.insert(collections.begin() + index, s);
        
        Action a{.ac = ActionType::remove};
        if (index == 0)
        {
            a.index = 0;
        }
        else
        {
            a.index = index - 1;
        }
        actions.push_back(a);
    }
    
    // Must be undoable
    void remove_bofore(int index)
    {
        //...
        Action a{.ac = ActionType::insert,  .s = collections[index], .index = index};
        collections.erase(collections.begin() + index);
        actions.push_back(a);
    }
    
    void undo()
    {
        // Attempts to revert back to the last operation
        if (actions.empty())
        {
            return;
        }
        
        auto action = actions.back();
        actions.pop_back();
        
        if (action.ac == ActionType::remove)
        {
            collections.erase(collections.begin() + action.index);
        }
        else if (action.ac == ActionType::insert)
        {
            collections.insert(collections.begin() + action.index, action.s);
        }
    }
    
    void print() const
    {
        std::cout << "size:" << collections.size() << " action size:" << actions.size() << std::endl;
    }
    
    std::vector<std::string> collections;
};

int main()
{
    StringList sl;
    
    sl.insert_before(0, "a");
    sl.print();
    sl.undo();
    
    sl.insert_before(0, "b");
    sl.print();
    sl.undo();
    
    sl.insert_before(0, "c");
    sl.print();
    sl.undo();
    
    sl.insert_before(0, "d");
    sl.insert_before(0, "e");
    sl.print();
    sl.undo();
    sl.undo();
    sl.print();

    return 0;
}

评论

0赞 HolyBlackCat 9/28/2023
我想知道如果您想撤消 .clear()
0赞 billz 9/28/2023
@HolyBlackCat很容易实现,不是吗?您可以添加插入_before操作列表,并在循环中执行此操作。clear()
0赞 HolyBlackCat 9/28/2023
嗯,但是你需要以某种方式对它们进行分组,这仍然不如移动整个向量有效。在这里拥有某种多态性会有所帮助,无论是还是(这个与你所做的最相似)还是其他什么。std::functionstd::variant