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

时间:2015-03-19 21:42:28

标签: c++ templates interface

我一直在寻找一种方法来做到这一点,我不确定它是否可能。我有一个Java类,它将一般类型接口的实例作为其构造函数的一部分,我想在C ++中重新创建它(它是一个在许多情况下很方便的实用程序类)。据我所知,与C ++中的接口最接近的等价物是纯虚拟类,而且(有些)等同于泛型是模板。

所以我想说我有一些类定义如下:

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();
}

基本上,让一个类的工作是获取对象列表,运行它们,然后返回它们的结果列表,同时不知道对象和结果的类型(假设对象有'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

5 个答案:

答案 0 :(得分:3)

您基本上(尝试)重新创建std::generate的功能。区别在于generate并不依赖于名为run的成员函数的有些笨重的约定。相反,它会调用类似函数的东西(虽然它可能会,但通常会重载operator())。

我们还可以(经常)通过在lambda表达式中定义类来避免单独定义您已命名为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的结果。

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

唯一的概念错误是在通过对象调用虚函数时尝试获取多态行为,而不是指针或对所述对象的引用。在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您的代码的固定版本。

需要注意的另一件事是:在派生类中使用覆盖虚函数时,请使用同名keyword标记覆盖。这有助于编译器为您提供帮助。

答案 3 :(得分:0)

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

可能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;
}

NB。我没有提出任何声明是否比使用其他答案建议的不同模式更好或更差;这只是您尝试编写的代码的工作版本。

答案 4 :(得分:0)

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

我还想提一下,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;
    }
}