【问题标题】:Storing variable arguments to be forwarded later存储稍后要转发的变量参数
【发布时间】:2019-03-13 03:55:05
【问题描述】:

我正在开发一个使用动作队列顺序执行动作的小程序。

我希望能够在我的操作中存储参数直到它们被执行(然后应该可以从操作的exec() 方法访问参数)。

下面有一个小例子:

#include <tuple>
#include <iostream>
#include <memory>
#include <utility>
#include <queue>

/**
 * Action interface
 */
struct Action {
    Action() {}
    virtual void exec() = 0;
};


/**
 * This action creates an object which type is given as template
 * and passes the parameters given to its ctor. On completion, it
 * prints its ID.
 */
template<class T, class... Ps>
struct CustomAction : public Action {
    // trying to store the variable arguments
    std::tuple<Ps...> _args;
    int _actionId;

    CustomAction(int id, Ps&&... args) : _actionId(id), 
        _args(std::make_tuple(std::forward<Ps>(args)...)) {
    }

    virtual void exec() override {
        T* item = new T(std::forward<Ps>(_args)...);

        std::cout << "done " << _actionId << std::endl;
    }
};

struct ActionQueue {

    std::queue<Action*> _queue;

    ActionQueue() {

    }

    void exec() {
        while(_queue.size()) {
            auto action = _queue.front();
            action->exec();
            _queue.pop();
        }
    }

    template<class T, class... Ps>
    void push(Ps&&... args) {
        auto action = new T(std::forward<Ps>(args)...);

        _queue.push(action);
    }
};

/**
 * Example item that is to be created. Nothing important here
 */
struct Item {

    int _b;

    Item(int b) : _b(b) {

    }
};

int main() {


    ActionQueue aq;

    int actionId = 5;
    int param = 2;

    aq.push<CustomAction<Item>>(actionId, param);

    // do stuff

    aq.exec();
}

在这个例子中,我创建了一个ActionQueue。我在队列中推送了一个新的CustomAction。这个动作只是创建了一个Item 并给它的ctor 提供了我在将动作推送到动作队列时给出的参数。

我的问题是我不知道为什么push() 方法的参数不能在CustomAction 类中使用。

编译上面的例子给我以下错误:

<source>:56:27: error: no matching constructor for initialization of 'CustomAction<Item>'

        auto action = new T(std::forward<Ps>(args)...);

                          ^ ~~~~~~~~~~~~~~~~~~~~~~

<source>:82:8: note: in instantiation of function template specialization 'ActionQueue::push<CustomAction<Item>, int &, int &>' requested here

    aq.push<CustomAction<Item>>(actionId, param);

       ^

<source>:27:5: note: candidate constructor not viable: requires single argument 'id', but 2 arguments were provided

    CustomAction(int id, Ps&&... args) : _actionId(id), 

    ^

<source>:22:8: note: candidate constructor (the implicit copy constructor) not viable: requires 1 argument, but 2 were provided

struct CustomAction : public Action {

       ^

<source>:22:8: note: candidate constructor (the implicit move constructor) not viable: requires 1 argument, but 2 were provided

1 error generated.

Compiler returned: 1

错误表明CustomAction 需要一个参数,而给出了两个参数,但CustomAction 应该接受args 中的第二个参数。

我在这里做错了什么?

谢谢

【问题讨论】:

  • 也许这会有所帮助? stackoverflow.com/a/10766422/1594913
  • 或者这个:stackoverflow.com/questions/7858817/…T(std::forward&lt;Ps&gt;(_args)...) 不会解包元组
  • C++11、C++14 还是 C++17?
  • 为什么不使用基于存储 lambda 的设计?那是std::queue&lt;std::function&lt;void())&gt;&gt;
  • 在上面的代码中,Action struct 必须有一个虚析构函数。当您从队列中删除项目时,您需要删除它们。首选的解决方案是使用智能指针(例如std::unique_ptr&lt;Action&gt;.

标签: c++ templates c++17 variadic-templates variadic-functions


【解决方案1】:

我在这里做错了什么?

嗯...首先你需要解压元组;例如,使用辅助函数,如下所示(std::index_sequencestd::make_index_sequence 可从 C++14 获得;您正在编译 C++17,因此您可以使用它们)

template <std::size_t ... Is>
void exec_helper (std::index_sequence<Is...> const &)
 { T* item = new T{std::get<Is>(_args)...}; }

virtual void exec() override {
    exec_helper(std::make_index_sequence<sizeof...(Ps)>{});

    std::cout << "done " << _actionId << std::endl;
}

但还有其他问题。

例如:您只能对函数/方法的模板参数使用完美转发,因此不能在此构造函数中使用

CustomAction(int id, Ps&&... args) : _actionId(id), 
    _args(std::make_tuple(std::forward<Ps>(args)...)) {
}

因为Ps... 是类的模板参数,而不是构造函数的参数。

应该像

template <typename ... Us>
CustomAction (int id, Us && ... args) : _actionId{id}, 
    _args{std::forward<Us>(args)...}
 { }

我猜是这样的

aq.push<CustomAction<Item>>(actionId, param);

应该是

// ........................VVV
aq.push<CustomAction<Item, int>>(actionId, param);

【讨论】:

  • 我以前从未听说过std::index_sequence,它允许您从get 解压。它有效,谢谢!
【解决方案2】:

用 cmets inline 重构和纠正:

#include <tuple>
#include <iostream>
#include <memory>
#include <utility>
#include <queue>

/**
 * Action interface
 */
struct Action {
    Action() {}

    // Comment #8 - virtual destructor required for polymorphic objects!
    virtual ~Action() {}
    virtual void exec() = 0;
};


/**
 * This action creates an object which type is given as template
 * and passes the parameters given to its ctor. On completion, it
 * prints its ID.
 */
template<class T, class... Ps>
struct CustomAction : public Action {
    // Comment #1 - Ps will be full types. Not references.
    // trying to store the variable arguments
    std::tuple<Ps...> _args;
    int _actionId;

    // Comment #2 - deduced arguments in the constructor allow perfect forwarding.
    template<class...Args>
    CustomAction(int id, Args&&... args) 
    : _actionId(id)
    , _args(std::forward<Args>(args)...) {
    }

    virtual void exec() override {
        // Comment #3 - The use of a lambda allows argument expansion for a constructor.
        auto create = [](auto&&...args)
        {
            return new T(std::forward<decltype(args)>(args)...);
        };

        // Comment #4 - c++17's std::apply allows us to call the lambda with arguments forwarded out of the tuple
        T* item = std::apply(create, std::move(_args));
        // Comment #9 do something with this pointer...

        std::cout << "done " << _actionId << std::endl;
    }
};

struct ActionQueue {

    std::queue<Action*> _queue;

    ActionQueue() {

    }

    void exec() {
        while(_queue.size()) {
            auto action = _queue.front();
            action->exec();
            _queue.pop();
        }
    }

    // Comment #5 - separate concerns - the queue only cares about the Action interface
    void push(Action* pa)
    {
        _queue.push(pa);
    }
};

/**
 * Example item that is to be created. Nothing important here
 */
struct Item {

    int _b;

    Item(int b) : _b(b) {

    }
};

// Comment #6 - A helper function to deduce stored argument types
template<class Action, class...Args>
auto makeCustomAction(int id, Args&&...args)
{
    using CustomActionType = CustomAction<Action, std::decay_t<Args>...>;
    return new CustomActionType(id, std::forward<Args>(args)...);
}

int main() {


    ActionQueue aq;

    int actionId = 5;
    int param = 2;

    // Comment #7 - store-and-forwarded arguments are now auto deduced.
    aq.push(makeCustomAction<Item>(actionId, param));

    // do stuff

    aq.exec();
}

https://godbolt.org/z/ZLkfi5

【讨论】:

  • 作为原始代码,该解决方案存在内存泄漏......请参阅我对问题的评论。
  • 移动成员​​如果调用两次可能会导致意外结果。
  • 两个 cmets 都注意到并同意了。我想我必须在某处划清界限,否则我最终会发布整个执行队列实现。 @Jarod42 是的,我故意假设 OP 的意图是完美地转发单次执行的参数。
  • @Phil1970 添加了虚拟析构函数。
猜你喜欢
  • 2018-07-13
  • 2012-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多