【问题标题】:Avoiding code repetition when iterating through an IShellItemArray迭代 IShellItemArray 时避免代码重复
【发布时间】:2020-06-21 23:16:36
【问题描述】:

我正在编写一个作用于IShellItemArrays 的大型函数库。实际上,所有函数都需要单独访问数组中的每个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或仿函数。

标签: c++ templates winapi dry


【解决方案1】:

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

【讨论】:

  • 模板方法有什么缺点吗?看起来你和 Raymond 都喜欢 std::function 方法,所以也许我会使用它,但至少现在模板策略对我来说似乎更具可读性。
  • 另外,std::function 方法能否与常规命名函数一起使用,而不是 lambda?
  • 是的,应该可以,但是 lambda 的优点是您可以从其封闭范围中捕获变量,如我在示例中所示。
  • 抱歉,刚看到你的第一个问题。模板方法的缺点是模板的每个不同实例都会复制代码。但这可能无关紧要,正如您所说,它更方便,并且也可以与常规函数一起使用。
  • @loop123123 我不打算偏袒其中一个。我说“你可以使用 A 或 B。”
【解决方案2】:

这是一种非模板方法。

您可以实现模板设计模式,同时重载调用运算符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 对所有类都是通用的,因此将始终被调用。请注意,最后会调用派生的调用运算符。

神奇的是参数列表从调用站点移到派生类构造函数中。请注意,Derived1 和 Derived2 具有不同的“参数列表”。

【讨论】:

  • 好吧,使用这种方法,命名函数(例如ProcessShelItem)可能是更好的选择,因为它可以将回调实现到现有类中并使代码更具可读性。
  • 请注意,这基本上是对 std::function 的重新发明,但存在一些隐藏的陷阱(例如,如果您复制 Base,您会得到一个切片,这不会很好地结束)。
猜你喜欢
  • 1970-01-01
  • 2011-11-18
  • 1970-01-01
  • 2014-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多