迭代IShellItemArray时避免代码重复

时间:2020-06-21 23:16:36

标签: c++ templates winapi dry

我正在编写一个对IShellItemArray起作用的函数库。实际上,所有功能都需要分别访问数组中的每个IShellItem(更具体地说,每个IShellItem2)。因此,除非我忽略了文档中的内容,否则我相信我必须这样做:

void SomeFunc(IShellItemArray* psia)
{
    HRESULT hr;

    DWORD cItems;
    hr = psia->GetCount(&cItems);
    if (FAILED(hr) || cItems == 0)
    {
        return;
    }

    for (UINT i = 0; i < cItems; ++i)
    {
        CComPtr<IShellItem> pShellItem;
        hr = psia->GetItemAt(i, &pShellItem);
        if (FAILED(hr))
        {
            continue;
        }

        CComPtr<IShellItem2> pShellItem2;
        pShellItem->QueryInterface(&pShellItem2);
        if (FAILED(hr))
        {
            continue;
        }

        // ...
    }
}

现在,我正在尝试抽象化该迭代,因此不必每次都编写它。到目前为止,我已经尝试创建一个ForEachShellItem帮助函数,该函数进行迭代并将所需的函数应用于每个项目,例如:

void ForEachShellItem(IShellItemArray* psia, HRESULT(*fn)(IShellItem2*))
{
    HRESULT hr;

    DWORD cItems;
    hr = psia->GetCount(&cItems);
    if (FAILED(hr) || cItems == 0)
    {
        return;
    }

    for (UINT i = 0; i < cItems; ++i)
    {
        CComPtr<IShellItem> pShellItem;
        hr = psia->GetItemAt(i, &pShellItem);
        if (FAILED(hr))
        {
            continue;
        }

        CComPtr<IShellItem2> pShellItem2;
        pShellItem->QueryInterface(&pShellItem2);
        if (FAILED(hr))
        {
            continue;
        }

        fn(pShellItem2);

    }
}

问题在于,如果必需的函数具有与该函数指针参数不同的签名,则该函数将不起作用。那么,有没有一种方法可以概括或模板化此方法?还是有其他策略可以避免重复执行迭代代码?感谢您的输入。

2 个答案:

答案 0 :(得分:2)

std::function和捕获的lambda可以做一些事情。因此,给定:

void ForEachShellItem(IShellItemArray*, std::function <HRESULT (IShellItem2 *)> fn)
{
    ...
    fn (si);
}

然后将附加参数传递给lambda,您可以执行以下操作:

void ForEachShellItem(IShellItemArray *isa, std::function <HRESULT (IShellItem2 *psi)> fn)
{
    ...
    HRESULT hr = fn (psi);
}

IShellItemArray isa = /* ... */;
int additional_param = 42;
ForEachShellItem (&isa, [additional_param] (IShellItem2 *psi)
    { std::cout << additional_param; return 0; });

要返回其他返回值,您可以执行以下操作:

IShellItemArray isa = /* ... */;
int additional_return_value = 0;
ForEachShellItem (&isa, [&additional_return_value] (IShellItem2 *psi)
    { additional_return_value = 43; return 0; });
std::cout << additional_return_value << "\n";

Live demo

您还可以通过模板传递其他参数并返回值。例如:

template <typename F, typename ... Args>
void ForEachShellItem(IShellItemArray*, F fn, Args && ... args)
{
    ...
    fn (si, std::forward <Args> (args)...);
}

IShellItemArray isa = /* ... */;
int additional_return_value = 0;
ForEachShellItem (&isa, [] (IShellItem2 *, int additional_param, int &additional_return_value)
    { std::cout << additional_param << "\n"; additional_return_value = 43; return 0; },
    42, additional_return_value);
std::cout << additional_return_value << "\n";

Live demo

答案 1 :(得分:0)

这是一种非模板方法。

您可以实现模板设计模式,并重载调用运算符http://sohaib94.github.io/cv/

这里是一个例子:

operator()

输出:

#include <iostream>

class Base
{
public:
    virtual ~Base() {}
    virtual void operator()() {}
    void GenericCaller()  // This would be your SomeFunc
    {
        std::cout << "this part is generic\n";

        operator()();  // This would be the custom function call
    }
};

class Derived : public Base
{
    int parm1, parm2;
public:
    Derived(int p1, int p2) : parm1(p1), parm2(p2) {}
    void operator()()
    {
        std::cout << "From Derived: " << parm1 << " " << parm2 << "\n";
    }
};

class Derived2 : public Base
{
    int parm1;
public:
    Derived2(int p1) : parm1(p1) {}
    void operator()()
    {
        std::cout << "From Derived2: " << parm1 << "\n";
    }
};

void caller(Base& b)
{
    b.GenericCaller();
}

int main()
{
    Derived d1(1, 2);
    Derived2 d2(3);
    caller(d1);
    caller(d2);
}

这种工作方式是this part is generic From Derived: 1 2 this part is generic From Derived2: 3 对所有类都是通用的,因此将始终被调用。请注意,最后将调用派生的call运算符。

魔术是将参数列表从调用站点移到派生类构造函数中。请注意,GenericCallerDerived1具有不同的“参数列表”。