【问题标题】:Is there a good way to implement a template interface in C++?有没有在 C++ 中实现模板接口的好方法?
【发布时间】:2015-03-19 21:42:28
【问题描述】:

我一直在寻找一种方法来做到这一点,但我不确定这是否可行。我在 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();
}

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

【问题讨论】:

  • 发布错误信息
  • 我贴出来了。很长很神秘。
  • 好吧,首先,虚函数应该具有相同的签名,因此返回类型的差异将不起作用。其次,如果你的接口是通过虚函数实现的,那么使用对象向量是行不通的,你需要指针才能使这种多态性工作
  • 嗯,除了使用 void 指针之类的操作之外,还有什么方法可以绕过需要相同签名的虚函数?

标签: c++ templates interface


【解决方案1】:

您基本上(试图)重新创建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的结果。

【讨论】:

  • 太棒了,我不知道这些功能。你知道如何让 std::generate 或 std::transform 运行多线程吗?
  • @Rae_III:既然你显然在使用gcc,你可能想看看它的parallel mode。
【解决方案2】:

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

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 标记重写。这有助于编译器帮助您。

【讨论】:

  • 感谢您的覆盖提示。之前也考虑过使用指针,但是试了一下还是不行,还是加了override就解决了。
【解决方案3】:

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

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

【讨论】:

  • 是的,我很抱歉过于简单化了,但这只是一个例子。我的代码实际上在它们自己的线程中运行每一个(一种自动的多线程方式)。但是,我不是在寻求线程方面的帮助,我只是想知道在 C++ 中执行此类操作的正确方法是什么(具有模板化接口)。
【解决方案4】:

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

vector&lt;shared_ptr&lt;AnInterface&lt;R&gt;&gt;&gt; 可能最符合您的意图。 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;
}

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-30
      • 2012-07-11
      • 2011-02-10
      • 2012-08-30
      相关资源
      最近更新 更多