如何编写一个接受具有相同接口的不同类型的参数的函数?

时间:2011-03-22 23:36:46

标签: c++ function interface arguments

考虑这个简化的例子:

#include <list>

typedef std::list<int> IntList;

class KindaIntList {
    public:
        IntList::const_iterator begin() const { /* do some stuff */ }
        IntList::const_iterator end() const { /* do some stuff */ }
        // ...etc
};

KindaIntList类实现了STL list的一些方法。

现在,我有一个功能

void f(IntList l) {
    // do stuff
}

仅调用KindaIntList实现的方法。我希望能够使用IntListKindaIntList参数调用它。这可能吗?

我考虑过使用模板,但f的定义非常大,我不想把它放在头文件中(f是一个类的成员,我不喜欢不希望它被内联。

修改

函数f实际上是另一个类的virtual成员;所以我不确定如何将它变成模板成员。

2 个答案:

答案 0 :(得分:4)

尽管您对模板存有疑虑,但这确实是使用C ++模板的合适位置。模板函数完美地捕获了“此函数适用于任何参数的概念,只要我对这些参数执行的操作是明确定义的。”

在这种情况下,您无需担心内联。除非你在类的主体内部定义 f,否则它不会自动内联,即使它是模板。例如,在此代码中:

class MyClass {
public:
     template <typename T> void f(T&);
};

template <typename T> void MyClass::f(T&) {
    /* ... implementation ... */
}

由于f正文中MyClass 未定义,因此不会将其视为内联函数。

至于你对使头文件太大的担忧,我认为这真的不用担心。如果你担心标题太大,你可以在中途发表一个大评论,如

 /* * * * * Implementation Below This Point * * * * */

或者,您可以为模板实现创建单独的.h文件,然后在头文件底部创建#include该文件。这可以防止客户看到模板实现,除非他们主动去寻找它。

希望这有帮助!

编辑:如果f是虚拟的,那么就无法使其成为模板功能(正如您可能已经想到的那样)。因此,如果你想使它适用于“看起来像std::list的东西”,那么你就没有很多好的选择。通常,您要为std::list和自定义列表类型创建基类,但这不是一个选项,因为您无法修改std::list

幸运的是,有一种方法可以使用名为 external polymorphism 的技巧来处理std::list和看似多态的事物。这个想法是,虽然你不能使适当的类表现为多态,但你可以通过引入一个多态类层次结构来为这些对象添加一个额外的间接层,该层次结构只是将所有请求转发给它们本身不是多态的对象。

如果你愿意拔出Big Template Guns,你可以将这个逻辑封装在一个类中,该类的工作方式与新的std::function模板类型相同。这个想法如下。首先,我们将创建一个多态基类,将您想要调用的所有函数导出为纯虚函数:

class List {
public:
    virtual ~List() {}

    virtual std::list<int>::const_iterator begin() const = 0;
    virtual std::list<int>::const_iterator end() const = 0;

    virtual void push_back(int value) = 0;

    /* ... etc. ... */
};

现在,我们可以定义List模板子类,它通过将所有调用转发给实际类型的对象来实现所有公共接口。例如:

template <typename T> class ListImpl: public List {
private:
    T& mImpl; // Actual object that does the work
public:
    /* Constructor stores a reference to the object that actually does the work. */
    ListImpl(T& impl) : mImpl(impl) {
         // Handled in initializer list
    }

    /* These functions all forward the requests to the implementation object. */
    virtual std::list<int>::const_iterator begin() const {
         return mImpl.begin();
    }
    virtual std::list<int>::const_iterator end() const {
         return mImpl.end();
    }
    virtual void push_back(int value) {
         mImpl.push_back(value);
    }

    /* ... etc. ... */
};

现在您已拥有此包装器,您可以实现f,以便它包含List

class MyClass {
public:
    void f(List* myList) {
        myList->push_back(137); // For example
    }
};

您可以通过首先将其包装在ListImpl类型的对象中,在一个看起来像列表的对象上调用此函数。例如:

MyClass mc;
std::list<int> myList;
MyIntList myIntList;

mc->f(new ListImpl<std::list<int> >(myList));
mc->f(new ListImpl<MyIntList>(myIntList));

当然,这是笨重而笨重的。您还必须担心资源泄漏,这不是很有趣。幸运的是,您可以通过包含所有逻辑来解决这个问题,以便在帮助程序类中处理ListListImpl,如下所示:

class ListWrapper {
public:
    template <typename ListType> ListWrapper(ListType& list) {
        /* Store a wrapper of the appropriate type. */
        mImpl = new ListImpl<ListType>(list);
    }

    /* Delete the associated implementation object. */
    ~ListWrapper() {
        delete mImpl;
    }

    /* For each interface function, provide our own wrapper to forward the logic
     * to the real implementation object.
     */
    std::list<int>::const_iterator begin() const {
        return mImpl->begin();
    }
    std::list<int>::const_iterator end() const {
        return mImpl->end();
    }
    void push_back(int value) {
        mImpl->push_back(value);
    }

    /* ... etc. ... */

    /* Copy functions necessary to avoid serious memory issues. */
    ListWrapper(const ListWrapper& rhs) {
        mImpl = rhs.mImpl->clone();
    }
    ListWrapper& operator= (const ListWrapper& rhs) {
        if (this != &rhs) {
             delete mImpl;
             mImpl = rhs.mImpl->clone();
        }
        return *this;
    }

private:
    List* mImpl; // Pointer to polymorphic wrapper
};

您现在可以写f来接收ListWrapper这样的内容:

class MyClass {
public:
    virtual void f(ListWrapper list) {
        list.push_back(137); // For example
    }
};

(这假设您已使用虚拟List函数更新了ListImplclone,该函数生成了对象的副本,为简洁起见,我省略了该函数。 / p>

奇迹般地,这段代码现在合法(而且安全!):

MyClass mc;
std::list<int> myList;
MyIntList myIntList;

mc.f(myList);
mc.f(myIntList);

此代码有效,因为ListWrapper的模板构造函数将自动推断其参数的类型,并隐式创建适合该对象的ListImpl类型的对象。它还为您封装了内存管理,因此您永远不会看到任何明确的newdelete。而且,这意味着你可以传入你想要的任何对象,一切都会自动运行 - 我们基本上通过使用并行类层次结构制作了看起来像list多态的任何东西!

呼!那很有趣!希望这有帮助!

答案 1 :(得分:1)

您可以将f重载为IntListKindaIntList,如下所示:

void f(IntList l){...}
void f(KindaIntList l){...}

或者让它采用迭代器:

void f(IntList::iterator first, IntList::iterator last){...}

也就是说,对于这两种情况,模板确实是最好的选择:

template<class ListT>
void f(ListT l){...}
template<class Iter>
void f(Iter first, Iter last){...}