将给定的vararg参数传递给另一个函数的合适方法是什么?

时间:2019-07-03 12:16:44

标签: c++ visual-c++ event-handling variadic-functions

我已经开始在C ++中的挂钩/事件系统上工作。该系统应该能够处理应用程序其他部分所通知的各种事件。

我所面临的问题是我希望其运行的方式。 通常,我希望这样,以便您使用希望传递的所有参数调用特定函数,然后该函数处理调用该特定事件的所有已注册钩子,将它们传递给参数,检索其结果值并返回原始呼叫者。

通常,这是它的外观:

CHookReturn* bInitializationStatus = Hook::Run("Initialize", gGame);
CHookReturn* bThinkSuccessful = Hook::Run("Think");



但是,我遇到了一个问题。 我以如下方式进行设置:Hook命名空间中的Run函数(调用CHookData_t结构的Run函数)需要传递varargs。我找不到其他办法。最终是这样的:

union CHookReturn
{
    const char* m_pszValue;
    int m_iValue;
    float m_flValue;
    double m_dlValue;
    bool m_bValue;
};

struct CHookData_t
{
    virtual void Run(CHookReturn* ret, ...) = 0;
};

namespace Hook
{
    std::unordered_map<const char*, std::unordered_map<const char*, CHookData_t*>> umHookList;

    bool Add(const char*, const char*, CHookData_t*);
    bool Exists(const char*, const char*);
    bool Remove(const char*, const char*);
    int Count(const char*);
    CHookReturn* Run(const char*, ...);
};

Hook :: Run函数的CPP文件段:

CHookReturn* Hook::Run(const char* eventName, ...)
{
    // FIXME: Look into alternative execution.
    // This code seems more like a workaround
    // than what I originally wanted it to be.

    int count = Hook::Count(eventName);
    CHookReturn* returnValues = new CHookReturn[count];
    int c = 0;

    unordered_map<const char*, CHookData_t*>::iterator itr;
    unordered_map<const char*, CHookData_t*> res;
    res = umHookList.at(eventName);

    va_list valist;
    void* args;
    va_copy(args, valist);

    for (itr = res.begin(); itr != res.end(); itr++)
    {
        CHookReturn returnData;
        itr->second->Run(&returnData, args);

        returnValues[c] = returnData;
        ++c;
    }

    return returnValues;
}

上面的代码提出了两个警告,这些警告使我怀疑以这种方式执行它是否是一个好主意,以及是否有其他选择我应该考虑。

我收到的警告是:
Warning C6001 Using uninitialized memory 'valist'.
Warning C6386 Buffer overrun while writing to 'returnValues': the writable size is 'count*8' bytes, but '16' bytes might be written.

有更好的方法吗?

1 个答案:

答案 0 :(得分:1)

使用va_list修改代码:

struct CHookData_t
{
    virtual ~CHookData_t() {}
    virtual void Run(CHookReturn* ret, va_list arg) = 0;
};

namespace Hook
{
    using HooksList = std::unordered_map<
         std::string, 
         std::unordered_map<std::string, std::unique_ptr<CHookData_t>>;

    HooksList umHookList;
    ...
}

std::vector<CHookReturn> Hook::Run(const std::string& eventName, ....)
{
    va_list valist;
    va_start(valist, eventName);

    auto result = Hook::RunVarg(eventName, valist);

    va_end(valist);

    return result;
}

std::vector<CHookReturn> Hook::RunVarg(const std::string& eventName, va_list arg)
{
    int count = Hook::Count(eventName);
    std::vector<CHookReturn> returnValues(count);

    size_t c = 0;
    for (auto& item : umHookList.at(eventName))
    {
        va_list arg_copy;
        va_copy(arg_copy, arg);
        item.second->Run(&returnValues[c], arg_copy);
        va_end(arg_copy);
        ++c;
    }

    return returnValues;
}

我不知道Hook::Run指向va_list的参数是什么,所以我不能为您提供不错的C ++解决方案。

请注意,循环内需要va_copy,因为某些编译器(不记得是哪个,可能是msvc)va_list的行为类似于指针,从中读取参数将对每次迭代产生影响。在其他编译器上,va_list的行为类似于值,而va_copy的行为不变。

offtopic :您的代码使用了很多C语言,如果您使用的是C ++ 17,则不应使用const char*std::stringstd::string_view,最好使用可变参数模板或va_args,而不要使用std::initializer_list,而避免使用偏向std::unique_ptrstd::shared_ptr的原始指针。我已经对您的代码进行了一些微调以解决这个问题。 同样,Hook不应是namespace,从它包含的函数和变量的外观来看,它应该是一个类,因此也应对其进行修复。