在迭代 IShellItemArray 时避免代码重复

Avoiding code repetition when iterating through an IShellItemArray

我正在编写一个作用于 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);

    }
}

问题是,如果必要的函数与函数指针参数的签名不同,那将不起作用。那么有没有一种方法可以概括或模板化这种方法呢?或者是否有任何其他策略来避免重复迭代代码?感谢您的任何输入。

您可以使用 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; });

并且要 return 一个额外的 return 值,你可以这样做:

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

您还可以通过模板传递额外的参数和 return 值。例如:

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

这是一种非模板方法。

您可以实现模板设计模式,同时重载调用运算符,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

它的工作方式是 GenericCaller 对所有 classes 是通用的,因此将始终被调用。请注意,最后会调用派生的调用运算符。

神奇的是参数列表从调用站点移到了派生的 class 构造函数中。请注意 Derived1Derived2 具有不同的“参数列表”。