声明带有返回模板的接口的C ++错误

时间:2018-11-01 19:42:50

标签: c++ class

我有一个基本接口,像这样的声明-IBaseTest.h:

#pragma once
template <class T1>
class IBaseTest
{
public:

    virtual ~IBaseTest();
    virtual T1 DoSomething() = 0;
};

和两个在BaseTest1.h中覆盖DoSomething()CBaseTest1类别的孩子:

#pragma once
#include "IBaseTest.h"
class CBaseTest1: public IBaseTest<int>
{
public:
    virtual int DoSomething();
};

BaseTest1.cpp:

#include "BaseTest1.h"

int CBaseTest1::DoSomething()
{
    return -1;
}

和-BaseTest2.h中的CBaseTest2

#pragma once
#include "IBaseTest.h"
class CBaseTest2: public IBaseTest<long long>
{
public:
    virtual long long DoSomething();
};

BaseTest2.cpp:

#include "BaseTest2.h"

long long CBaseTest2::DoSomething()
{
    return -2;
}

因此CBaseTest1 :: DoSomething()会将返回类型重写为int,而CBaseTest2 :: DoSomething()则将long类型重写为long long。现在,我想使用一个指向基本接口的指​​针,以使用这些类,在那里我遇到了问题:

#include "IBaseTest.h"
#include "BaseTest1.h"
#include "BaseTest2.h"


int _tmain(int argc, _TCHAR* argv[])
{
    IBaseTest<T1> * pBase = NULL;

    pBase = new CBaseTest1();

    cout << pBase->DoSomething() << endl;

    pBase = new CBaseTest2();

    cout << pBase->DoSomething() << endl;

    getchar();

    return 0;
}

问题是我无法声明IBaseTest<T1> * pBase = NULL; T1未定义。如果像这样在_tmain之前声明模板:

template <class T1>

int _tmain(int argc, _TCHAR* argv[])
{ 
  ...
}

我得到:error C2988: unrecognizable template declaration/definition

那我要放在这里而不是T1呢?

IBaseTest<??> * pBase = NULL;

2 个答案:

答案 0 :(得分:0)

问题是实例化模板类IBaseTest的对象时需要知道T1参数。从技术上讲,IBaseTest<int>IBaseTest<long long>是两种没有共同基础的不同类型,并且C ++不允许您声明变量IBaseTest<T1> pBase = NULL;,其中T1是在运行时确定的。您试图实现的是一种可以在动态类型化语言中实现的功能,但在C ++中则无法实现,因为它是静态类型。

但是,如果每次调用该方法时都知道DoSomething的预期返回类型,则可以使示例工作。首先,您需要引入一个不是模板的通用基类:

#include <typeinfo>
#include <typeindex>
#include <assert.h>

class IDynamicBase {
public:
  virtual std::type_index type() const = 0;

  virtual void doSomethingVoid(void* output) = 0;

  template <typename T>
  T doSomething() {
    assert(type() == typeid(T));
    T result;
    doSomethingVoid(&result);
    return result;
  }

  virtual ~IDynamicBase() {}
};

请注意,它有一个名为doSomething template 方法,该方法采用类型参数作为返回值。这是我们稍后将调用的方法。

现在,修改您以前的IBaseTest以扩展IDynamicBase

template <class T1>
class IBaseTest : public IDynamicBase
{
public:
  std::type_index type() const {return typeid(T1);}

  void doSomethingVoid(void* output) {
    *(reinterpret_cast<T1*>(output)) = DoSomething();
  }

  virtual T1 DoSomething() = 0;
  virtual ~IBaseTest() {}
};

您不需要更改CBaseTest1CBaseTest2

最后,您现在可以像这样在主函数中编写代码:

  IDynamicBase* pBase = nullptr;

  pBase = new CBaseTest1();

  std::cout << pBase->doSomething<int>() << std::endl;

  pBase = new CBaseTest2();

  std::cout << pBase->doSomething<long long>() << std::endl;

请注意,我们现在调用pBase->DoSomething()而不是调用pBase->doSomething<T>(),其中T是一种必须在调用方法时必须静态知道的类型,并在调用站点提供该类型,例如pBase->doSomething<int>()

答案 1 :(得分:0)

该语言不允许直接执行您要尝试执行的操作。此时,您应该问自己是否是解决问题的正确方法。

假设每种类型的操作没有太多不同的操作,第一种可能很好用的方法是简单地在函数本身中执行操作,而不是返回通过继承不相关的类型。

class IBaseTest
{
public:
    virtual void OutputTo(std::ostream &os) = 0;
};

class CBaseTest1
{
public:
    virtual void OutputTo(std::ostream &os) override;
private:
    int DoSomething();
};

void CBaseTest1OutputTo(std::ostream &os)
{
    os << DoSomething() << std::endl;
}

如果只有少数几种类型但有很多操作,则可以改用visitor模式。

如果您的操作主要取决于类型,则可以使用:

class IVisitor
{
public:
    virtual void Visit(int value) = 0;
    virtual void Visit(long value) = 0;
};

否则,请使用更通用的

class IVisitor
{
public:
    virtual void Visit (CBaseTest1 &test1) = 0;
    virtual void Visit (CBaseTest2 &test2) = 0;    
};

然后在您的班级中添加一个apply函数

class IBaseTest
{
public:
    virtual void Apply(IVisitor &visitor) = 0;
};

在每个派生类中,实现Apply函数:

void CBaseTest1 : public IBaseTest
{
    virtual void Apply(IVisitor &visitor) override 
    {
        visitor.Visit(this->DoSomething()); // If you use first IVisitor definition
        visitor.Visit(*this);               // If you use second definition
};

出于创建目的,如果您需要从某个文件中创建类,那么可以创建一个工厂,该工厂从type标签中返回相应的类。

假设您每次都需要一个新对象的示例:

 enum class TypeTag { Integer = 1, LongInteger = 2 };
std::unique_ptr<IBaseTest> MakeObjectForTypeTag(TypeTag typeTag)
{
    switch (typeTag)
    {
        case TypeTag::Integer : return new CBaseTest1();
        case TypeTag::LongInteger : return new CBaseTest2();
    }
}

所以只有在创建对象时才执行switch语句……您也可以为此使用映射甚至数组...

正确的方法取决于您的实际问题。

  • 您有多少个CBaseClass *?
  • 您是否希望添加其他课程?经常吗?
  • 您有多少类似DoSomething()的操作?
  • 您对DoSomething的结果执行多少操作?
  • 您是否希望添加其他操作?经常吗?

通过回答这些问题,做出正确的决定会容易得多。如果动作是稳定的(只有几个),则更适合使用OutputTo之类的特定虚拟功能。但是,如果您有许多操作,但不希望对ITestBase类层次结构进行太多更改,则访问者解决方案更为合适。

给定解决方案在给定上下文中更合适的原因主要是将来添加类或动作时的维护工作。您通常希望最频繁的更改(添加类或操作)要求在代码中的所有位置进行更改。