【问题标题】:Declaring template functions in a C++ interface?在 C++ 接口中声明模板函数?
【发布时间】:2018-01-10 20:09:28
【问题描述】:

为了演示,假设我有一些动物类,每个类都派生自一个“动物”类,每个“知道”它们是什么类型,并且每个都具有某种独特的能力:

enum class animal_type { antelope, bear, cat };

class animal
{
};

class antelope : public animal
{
public:
    static const animal_type type = animal_type::antelope;
    void run() { std::cout << "antelope runs\n"; };
};

class bear : public animal
{
public:
    static const animal_type type = animal_type::bear;
    void roar() { std::cout << "bear roars\n"; };
};

class cat : public animal
{
public:
    static const animal_type type = animal_type::cat;
    void meow() { std::cout << "cat meows\n"; };
};

现在,我希望能够根据动物的类型检索动物:

class animal_getter
{
public:
    animal& get(animal_type t)
    {
        static antelope s_antelope;
        static bear s_bear;
        static cat s_cat;

        switch (t)
        {
            case animal_type::antelope:
                return s_antelope;

            case animal_type::bear:
                return s_bear;

            case animal_type::cat:
                return s_cat;
        }
    }
};

最后,返回动物的实际类型,让调用语法更好:

template<typename T>
T& get()
{
    return static_cast<T&>(get(T::type));
}

现在我可以这样写了:

animal_getter ag;
ag.get<antelope>().run();

而不是冗长的:

animal_getter ag;
static_cast<antelope&>(ag.get(animal_type::antelope)).run();

我希望这没有什么太不合理的地方。但是现在我希望能够对获取动物进行单元测试,因此理想情况下可以伪造 animal_getter 类(想象实际实现访问数据库或您在单元测试中不想要的东西,因此是假的) .因此,最好为“animal getter”类定义一个接口,然后创建一个实现该接口的假货。那么问题来了,这个界面可以写吗?这不会编译:

struct IAnimalGetter
{
    virtual template<typename T> T& get() = 0;
};

是否有可能挽救这个想法或者模板函数永远不能被声明为虚拟函数来定义包含它们的接口?

如果这个想法是行不通的,这是从什么时候开始出错的?是在编写模板函数时,它自己进行了转换?我是否应该在返回动物对象的东西上停下来,然后让调用者负责演员表?

【问题讨论】:

  • 不清楚你为什么需要这个。只需公开您的静态数据成员即可。 ag.get&lt;antelope&gt;().run() 什么都不做 ag.s_antelope().run() 不做。
  • (续)没有虚拟模板,所以虚拟get template 是不可能的。您可以通过访问者模式和/或 ctrategicalky 放置的演员来解决这个问题,但仍然不清楚您到底想要实现什么。 get&lt;antelope&gt;() 买给你什么get_antelope() 买不到?
  • 如果派生类没有任何共同点,也许错误是基类为空?需要强制转换或切换类型都是模型存在问题的迹象。羚羊当然不会沉默,熊会跑。
  • 谢谢大家。我正在处理的实际代码远比这复杂得多——为了简洁起见,我只是想尽可能简单地演示这个问题。请不要将此示例代码视为我正在尝试做的事情的说明,即专门用于基于接口创建假对象。

标签: c++ unit-testing templates interface


【解决方案1】:

模板、强制转换和类型标签在声明的用例中似乎没有任何用处(获取动物,然后立即利用其独特特征)。

antelope& get_antelope();
bear& get_bear();
cat& get_cat();

这就是我们所需要的。

get_antelope().run();
get_cat().meow();

如果需要,这些函数可以是独立的或成员的、虚拟的。每个返回类型需要一个函数。这与每个返回类型的 switch 语句中的一种情况没有太大区别,并且在某些方面优于这种情况。

如果出于某种尚未指定的原因,您确实需要一个函数模板,您可以基于普通函数定义一个。

 template <class A>
 A& get_animal();

 template<> antelope& get_animal<antelope>() { return get_antelope(); }
 template<> bear& get_animal<bear>() { return get_bear(); }
 template<> cat& get_animal<cat>() { return get_cat(); }

【讨论】:

  • 我的示例代码事后看来,不是很容易解释。 get 函数的主体应该只是一个注释:“// 以某种方式获取动物”。我现实没有开关。每次发布都会添加新类型,每次添加一个 get_new_animal_type 会很容易出错且是不必要的负担。
  • @WalderFrey 我已经回答了您提出的问题。如果您有不同的问题,请单独提出。
  • 我不是在问另一个问题。抱歉,如果评论似乎是一个新问题。我只是想解释为什么你的答案在我的情况下似乎不合适。我对此很陌生:也许投反对票比发表评论更好?无论如何,感谢您花时间回答,我很感激。
  • @WalderFrey 我只能看到您发布的代码。我不知道我看不到的代码可能是什么问题。我认为答案确实回答了发布的问题。如果这不是您想要回答的问题,您可以尝试提出更好的问题。
【解决方案2】:

不允许使用虚拟模板函数,但是你的模板函数没有逻辑,所以在这种情况下我会使用:

struct IAnimalGetter
{
    virtual animal& get(animal_type t) = 0;

    template<typename T>
    T& get()
    {
      return static_cast<T&>(get(T::type));
    }
};

还有:

class animal_getter : public IAnimalGetter
{
public:
    animal& get(animal_type t)
    {
        // implementation
    }
};

class mock_animal_getter : public IAnimalGetter
{
public:
    animal& get(animal_type t)
    {
        // mock implementation
    }
};

【讨论】:

  • 我曾考虑过这一点,但对拥有一个不包含纯虚函数以外的任何东西的“接口”感到有点困惑。很高兴看到有人支持这是一种合法的做法。谢谢。
  • 另一种选择是提供非会员模板功能。这将使您的界面保持纯净,但会使您的代码不那么面向对象。
【解决方案3】:

使用类型转换破坏了此处使用模板的意义。 我不太清楚 getter 类的用途。它很清楚它用于保存某些预定类型的一些静态实例,但不清楚为什么它是一个处理所有类型的单个类。我看不到您获得太多收益,因为它没有太多代码,而且无论如何您都必须在调用中指定类型。 switch 语句是一个潜在的错误,如果你添加一个新的动物类型,它仍然可以编译,但是新的类型不会在 switch 中处理,你会得到一个运行时错误。

如果您不需要静态实例,只需构造正确的类型。

如果您确实需要它们,请考虑是否应由该类型管理它们(即,熊有获取熊实例的方法)。另一种选择(正如已经指出的那样)是公开你的 getter 持有的每种类型的静态实例。

如果您不想管理相关类中的静态实例,请考虑使用单独的 getter:

template<typename T>
struct IGetter
{
    virtual T Get() =0;
};

template<typename T>
struct RealGetter : public IGetter<T>
{
    static T item;
    virtual T Get()
    {
         return item;
    }
 };

template<typename T>
struct MockGetter : public IGetter<T>
{

   virtual T Get()
   {
     // some mock implementation
   }
};

【讨论】:

  • 请不要过多阅读示例代码 - 它是为了演示原理,而不是被挑选出来。实际问题以粗体突出显示 - 这是我真正要问的问题,使用代码作为工具来展示我面临的问题。在我正在处理的实际代码中,单个类当然没有静态实例——它们实际上是从内存数据库中获取的业务对象。此处的代码出于演示目的而被删除,并不真实。
  • 我知道这是简化的代码,但我认为您的 getter 已被简化得如此之多,以至于隐藏了您的意图。您的标题和粗体问题仍然很笼统,因此我认为涉及不同类结构(没有强制转换)的答案可能是最好的选择,但是您的示例很难确切地说明您想要什么。每个可获取类的模板是否有理由不符合您的实际要求?
  • 我正在使用遗留代码。我们有一个整体类,它充当通用业务对象的网关。你要一个吗?使用单体提供的功能(有点像我的例子中的animal_getter)!代码库很古老,没有兴趣将此功能移动到单个类型(熊、猫、蟑螂)中。模板 get 函数是使调用代码更清晰(无强制转换)的折衷方案,但它使接口的定义变得棘手。因此问题。希望对您有所帮助。
  • 那么如何使用我展示的东西,使用您的单一遗留方式实现 RealGetter 的内部? (不是我称之为 RealGetter)你可以通过一个接口调用它,并且可以在测试中将其子用于模拟。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多