在C++中有实现模板接口的好方法吗?

Is there a good way to implement a template interface in C++?

我一直在四处寻找实现此目的的方法,但我不确定是否可行。我在 Java 中有一个 class 将通用类型接口的实例作为其构造函数的一部分,我想在 C++ 中重新创建它(它是一个实用程序 class 这在很多情况下都很方便)。据我所知,C++ 中最接近接口的等价物是纯虚拟 class,而泛型的(有点)等价物是模板。

假设我有一些 class 定义如下:

template<typename R>
class AnInterface
{
    public:
        virtual R run() = 0;
        virtual ~AnInterface() {}
};

template<typename R>
class Processor
{
    public:
        Processor(std::vector<AnInterface<R>> toRun) : toRun(toRun) {}
        std::vector<R> process() {
            std::vector<R> res;
            for(int i = 0; i < this->toRun.size(); ++i)
               res.push_back(toRun[i].run());
            return res;
            }

    private:
        std::vector<AnInterface<R>> toRun;
};


class AnInstanceClass : public AnInterface<int>
{
    int run() { return 1+1; }
};

我希望能够和他们一起做这样的事情:

int main()
{
    std::vector<AnInterface<int>> toRun;
    toRun.push_back(AnInstanceClass());
    toRun.push_back(AnInstanceClass());
    Processor<int> p(toRun);
    std::vector<int> p.process();
}

基本上,有一个 class 的工作是获取对象列表,运行 它们,然后 return 它们的结果列表,同时对类型不可知对象和结果(假设对象具有 'run' 函数)。在 Java 中,我使用泛型和接口实现了这一点。我尝试在 C++ 中实现上述解决方案,但它无法编译并且编译器输出非常神秘,这表明我搞砸了该语言的一些非常基础的东西。我的 C++ 有点生疏,所以我不确定那是什么。像这样的东西如何在 C++ 中实现?

编辑:这是我尝试编译上述代码时的错误消息:

In file included from /usr/include/c++/4.8/vector:64:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/stl_vector.h: In instantiation of ‘class std::vector<AnInterface<int> >’:
test.cpp:36:36:   required from here
/usr/include/c++/4.8/bits/stl_vector.h:704:7: error: cannot allocate an object of abstract type ‘AnInterface<int>’
       resize(size_type __new_size, value_type __x = value_type())
       ^
test.cpp:4:7: note:   because the following virtual functions are pure within ‘AnInterface<int>’:
 class AnInterface
       ^
test.cpp:7:19: note:    R AnInterface<R>::run() [with R = int]
         virtual R run() = 0;
                   ^
test.cpp: In function ‘int main()’:
test.cpp:40:23: error: expected initializer before ‘.’ token
     std::vector<int> p.process();
                       ^
test.cpp: In instantiation of ‘Processor<R>::Processor(std::vector<AnInterface<R> >) [with R = int]’:
test.cpp:39:27:   required from here
test.cpp:15:68: error: no matching function for call to ‘std::vector<int, std::allocator<int> >::vector(std::vector<AnInterface<int> >&)’
         Processor(std::vector<AnInterface<R> > toRun) : toRun(toRun) {}
                                                                    ^
test.cpp:15:68: note: candidates are:
In file included from /usr/include/c++/4.8/vector:64:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/stl_vector.h:398:9: note: template<class _InputIterator> std::vector<_Tp, _Alloc>::vector(_InputIterator, _InputIterator, const allocator_type&)
         vector(_InputIterator __first, _InputIterator __last,
         ^
/usr/include/c++/4.8/bits/stl_vector.h:398:9: note:   template argument deduction/substitution failed:
test.cpp:15:68: note:   candidate expects 3 arguments, 1 provided
         Processor(std::vector<AnInterface<R> > toRun) : toRun(toRun) {}
                                                                    ^
In file included from /usr/include/c++/4.8/vector:64:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/stl_vector.h:310:7: note: std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = int; _Alloc = std::allocator<int>]
       vector(const vector& __x)
       ^
/usr/include/c++/4.8/bits/stl_vector.h:310:7: note:   no known conversion for argument 1 from ‘std::vector<AnInterface<int> >’ to ‘const std::vector<int, std::allocator<int> >&’
/usr/include/c++/4.8/bits/stl_vector.h:295:7: note: std::vector<_Tp, _Alloc>::vector(std::vector<_Tp, _Alloc>::size_type, const value_type&, const allocator_type&) [with _Tp = int; _Alloc = std::allocator<int>; std::vector<_Tp, _Alloc>::size_type = long unsigned int; std::vector<_Tp, _Alloc>::value_type = int; std::vector<_Tp, _Alloc>::allocator_type = std::allocator<int>]
       vector(size_type __n, const value_type& __value = value_type(),
       ^
/usr/include/c++/4.8/bits/stl_vector.h:295:7: note:   no known conversion for argument 1 from ‘std::vector<AnInterface<int> >’ to ‘std::vector<int, std::allocator<int> >::size_type {aka long unsigned int}’
/usr/include/c++/4.8/bits/stl_vector.h:256:7: note: std::vector<_Tp, _Alloc>::vector(const allocator_type&) [with _Tp = int; _Alloc = std::allocator<int>; std::vector<_Tp, _Alloc>::allocator_type = std::allocator<int>]
       vector(const allocator_type& __a)
       ^
/usr/include/c++/4.8/bits/stl_vector.h:256:7: note:   no known conversion for argument 1 from ‘std::vector<AnInterface<int> >’ to ‘const allocator_type& {aka const std::allocator<int>&}’
/usr/include/c++/4.8/bits/stl_vector.h:248:7: note: std::vector<_Tp, _Alloc>::vector() [with _Tp = int; _Alloc = std::allocator<int>]
       vector()
       ^
/usr/include/c++/4.8/bits/stl_vector.h:248:7: note:   candidate expects 0 arguments, 1 provided
In file included from /usr/include/c++/4.8/vector:69:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/vector.tcc: In instantiation of ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, const _Tp&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<AnInterface<int>*, std::vector<AnInterface<int> > >; typename std::_Vector_base<_Tp, _Alloc>::pointer = AnInterface<int>*]’:
/usr/include/c++/4.8/bits/stl_vector.h:913:28:   required from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; std::vector<_Tp, _Alloc>::value_type = AnInterface<int>]’
test.cpp:37:38:   required from here
/usr/include/c++/4.8/bits/vector.tcc:329:19: error: cannot allocate an object of abstract type ‘AnInterface<int>’
    _Tp __x_copy = __x;
                   ^
test.cpp:4:7: note:   since type ‘AnInterface<int>’ has pure virtual functions
 class AnInterface
       ^
In file included from /usr/include/c++/4.8/vector:69:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/vector.tcc:329:8: error: cannot declare variable ‘__x_copy’ to be of abstract type ‘AnInterface<int>’
    _Tp __x_copy = __x;
        ^
test.cpp:4:7: note:   since type ‘AnInterface<int>’ has pure virtual functions
 class AnInterface
       ^
In file included from /usr/include/x86_64-linux-gnu/c++/4.8/bits/c++allocator.h:33:0,
                 from /usr/include/c++/4.8/bits/allocator.h:46,
                 from /usr/include/c++/4.8/vector:61,
                 from test.cpp:1:
/usr/include/c++/4.8/ext/new_allocator.h: In instantiation of ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, const _Tp&) [with _Tp = AnInterface<int>; __gnu_cxx::new_allocator<_Tp>::pointer = AnInterface<int>*]’:
/usr/include/c++/4.8/ext/alloc_traits.h:216:9:   required from ‘static void __gnu_cxx::__alloc_traits<_Alloc>::construct(_Alloc&, __gnu_cxx::__alloc_traits<_Alloc>::pointer, const _Tp&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; __gnu_cxx::__alloc_traits<_Alloc>::pointer = AnInterface<int>*]’
/usr/include/c++/4.8/bits/stl_vector.h:906:34:   required from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; std::vector<_Tp, _Alloc>::value_type = AnInterface<int>]’
test.cpp:37:38:   required from here
/usr/include/c++/4.8/ext/new_allocator.h:130:9: error: cannot allocate an object of abstract type ‘AnInterface<int>’
       { ::new((void *)__p) _Tp(__val); }
         ^
test.cpp:4:7: note:   since type ‘AnInterface<int>’ has pure virtual functions
 class AnInterface
       ^
In file included from /usr/include/c++/4.8/vector:62:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, const _T2&) [with _T1 = AnInterface<int>; _T2 = AnInterface<int>]’:
/usr/include/c++/4.8/bits/stl_uninitialized.h:75:53:   required from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const AnInterface<int>*, std::vector<AnInterface<int> > >; _ForwardIterator = AnInterface<int>*; bool _TrivialValueTypes = false]’
/usr/include/c++/4.8/bits/stl_uninitialized.h:117:41:   required from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const AnInterface<int>*, std::vector<AnInterface<int> > >; _ForwardIterator = AnInterface<int>*]’
/usr/include/c++/4.8/bits/stl_uninitialized.h:258:63:   required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const AnInterface<int>*, std::vector<AnInterface<int> > >; _ForwardIterator = AnInterface<int>*; _Tp = AnInterface<int>]’
/usr/include/c++/4.8/bits/stl_vector.h:316:32:   required from ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >]’
test.cpp:39:27:   required from here
/usr/include/c++/4.8/bits/stl_construct.h:83:7: error: cannot allocate an object of abstract type ‘AnInterface<int>’
       ::new(static_cast<void*>(__p)) _T1(__value);
       ^
test.cpp:4:7: note:   since type ‘AnInterface<int>’ has pure virtual functions
 class AnInterface

你真的需要处理器class吗?我建议使用 std::transform

std::transform applies the given function to a range and stores the result in another range

您基本上(试图)重新创建 std::generate 的功能。不同之处在于 generate 不依赖于名为 run 的成员函数的有些笨拙的约定。相反,它会调用类似函数的东西(尽管它可能而且经常会是一个重载 operator())。

我们还可以(经常)通过在 lambda 表达式中定义 class 来避免单独定义您命名为 AnInstanceClass 的内容。

所以,在这种情况下,我们会看类似的东西:

std::vector<int> p;

std::generate_n(std::back_inserter(p), 2, [] { return 1 + 1; });

这基本上与线程无关,因此如果您想 运行 单独线程中的各个任务,您也可以很容易地做到这一点。 std::async 有一些注意事项,但无论您是否涉及 std::generate,它们都几乎相同。

请注意,这与@Severin 的回答略有不同——他提到的是 std::transform 而不是 std::generate。两者之间的基本区别在于 transform 获取一组输入,对其进行转换,然后生成一组这些输出。你的 AnInstance::run 只是产生输出(不接受任何输入)所以至少对我来说 std::generate 更合适。

如果你有这样的东西,

std::transform 会更有用:

std::vector<int> inputs { 1, 2, 3, 4, 5};
std::vector<int> results;

std::transform(inputs.begin(), inputs.end(), [](int in) { return in * 2; });

这应该会产生 2, 4, 6, 8, 10.

的结果

您遇到的唯一概念性错误是在通过对象而不是指针或对所述对象的引用调用虚函数时试图获得多态行为。在 C++ 中,要获得 运行 次多态性,您需要使用指针或引用。因此,Processor 应该像这样使用 std::vector<AnInterface<R>*>

template<typename R>
class Processor
{
    public:
        Processor(std::vector<AnInterface<R>*> toRun) : toRun(toRun) {}
        std::vector<R> process() {
            std::vector<R> res;
            for(int i = 0; i < this->toRun.size(); ++i)
               res.push_back(toRun[i]->run());
            return res;
            }

    private:
        std::vector<AnInterface<R>*> toRun;
};

Here's 代码的固定版本。

另一件需要注意的事情:在派生 class 中使用重写虚函数时,用同名 keyword 标记重写。这有助于编译器帮助你。

vector<AnInterface<R>> 不起作用,因为它会导致 slicing。这也是您的错误消息的原因,因为某些 vector 操作需要默认构造或复制构造对象,而这对于抽象 class 是不可能的。

可能 vector<shared_ptr<AnInterface<R>>> 最符合您的意图。 shared_ptr 是 C++ 最接近 Java 对象引用的东西。

这是基于您的示例代码的 C++11 工作代码。我想说的一点是,处理器目前按值获取其向量。如果它更符合您的设计,它可以通过引用,甚至通过移动来引用它。

#include <iostream>
#include <memory>
#include <vector>

template<typename R>
struct AnInterface
{
    virtual R run() = 0;
    virtual ~AnInterface() {}
};

template<typename R>
using AnInterfaceVector = std::vector< std::shared_ptr<AnInterface<R>> >;

template<typename R>
class Processor
{
    public:
        Processor(AnInterfaceVector<R> toRun) : toRun(toRun) {}

        std::vector<R> process()
        {
            std::vector<R> res;
            for (auto && r : toRun)
                res.push_back( r->run() );

            return res;
        }

    private:
        AnInterfaceVector<R> toRun;
};

struct AnInstanceClass : AnInterface<int>
{
    int run() override { return temp; }

    AnInstanceClass(int n): temp(n) {}
    int temp;
};

int main()
{
    AnInterfaceVector<int> toRun;

    toRun.emplace_back( std::make_shared<AnInstanceClass>(4) );
    toRun.emplace_back( std::make_shared<AnInstanceClass>(7) );

    Processor<int> p{toRun};
    auto results = p.process();

    for (auto && i : results)
        std::cout << i << " ";
    std::cout << std::endl;
}

注意。我不提供任何声明这比使用其他答案所建议的不同模式更好还是更坏;这只是您尝试编写的代码的工作版本。

正如其他答案中已经提到的,您的错误是尝试使用接口向量 (std::vector<AnInterface<int>>) 而不是指向接口的指针向量,如 std::vector<AnInterface<int>*> - 只有后者允许多态性,而您的版本会尝试存储实际的接口对象(这当然是不可能的,因为它们是抽象的 类)。

我还想提一下,Sean Parent 有一个很好的模式,它使您的 AnInstanceClass 无需从任何东西继承,只要它实现了具有正确名称的成员函数,并且签名。这非常方便,因为你可以,例如甚至使用不能从任何东西继承的 lambda 或普通函数(在将它们包装在 std::function 之后):

#include <vector>
#include <memory>
#include <iostream>
#include <algorithm>
#include <functional>

//R is the return type
template<class R>
class Processor {
public:
    //T can be anything, that has an ()-operator
    template<class T>
    void push_back(const T& arg) {
        todo.emplace_back(std::make_unique<runnable_imp<T>>(arg));
    }

    std::vector<R> process() {
        std::vector<R> ret;
        for (auto& e : todo) {
            ret.push_back(e->run());
        }
        return ret;
    }

private:
    struct runnable_concept {
        virtual R run()=0;
        virtual ~runnable_concept(){};
    };

    template<class T>
    struct runnable_imp :public runnable_concept {
        runnable_imp(T data) :data(data){};         
        virtual R run() override { return data(); }
        T data;
    };
    std::vector<std::unique_ptr<runnable_concept>> todo;
};

struct SomeClass {
    SomeClass(int arg) :arg(arg){};
    int operator()(){ return arg; }
    int arg;
};
int SomeFunction(){ return 30; }

int main()
{
    Processor<int> pr;
    pr.push_back([]{return 10; }); 
    pr.push_back(SomeClass(20)); 
    pr.push_back(std::function<int()>(SomeFunction));
    std::vector<int> res= pr.process();

    for (auto e : res) {
        std::cout << e << std::endl;
    }
}