【问题标题】:Implementing comparison using a generic interface in C++在 C++ 中使用泛型接口实现比较
【发布时间】:2010-11-14 03:09:14
【问题描述】:

假设我正在开发一个适用于 Item 类型项目的库。主要入口点是这样的类:

class Worker
{
private:
  SomeContainer _c;
public:
    void add( const Item &i );
    void doSomething();
};

doSomething() 方法查看添加的项目,比较它们等,并对它们进行处理。所以 Item 类需要一个operator==

现在,我想在不同的环境中使用这个库,并且在每个环境中,Item 类的实现都是不同的。因此,Item 需要一个虚拟比较函数:

class Item
{
protected:
    virtual bool _equals( Item &other ) = 0;
public:
    bool operator==( Item &other ) { return _equals( other ); };
};

每个环境都有自己的 Item 实现。该库只知道 Item 类,并且使用该库在特定于平台的应用程序中定义和实现特定的 item 类。在环境 A 中可能是:

class AItem: public Item
{
private:
    bool _equals( Item &other );
    std::string _member;
...
};

在环境 B 中:

class BItem: public Item
{
private:
    bool _equals( Item &other );
    int _member;
...
};

现在,对于每种环境,实现库使用的比较的最佳方法是什么? _equals是在Item类中指定的,所以其具体的item实现需要将other转换为自己的类型。

在给定的环境中,不会同时使用不同的项目类型,因此假设,以下是安全的:

bool AItem::_equals( Item &other )
{
    return this->_member == static_cast<AItem &>(other)._member;
}

但这似乎是一个令人讨厌的解决方案,因为它允许使用该库的程序员实现一个新环境,如果他将不同类型的项目添加到 worker 中来破坏事物。

我能想到的其他解决方案是:

  • 使用dynamic_cast
  • 通过将成员添加到指示环境的基本 Item 类来实现我自己的 RTTI 排序。然后可以在比较函数中使用它来查看other 是否属于同一类型。不是很优雅。
  • 实现库中的所有类型,并在编译时根据#define 选择正确的类型。这意味着库本身必须针对每个环境进行扩展。
  • 使用模板化工作器。

但我觉得必须有一个更优雅的解决方案。也许是完全不同的东西。你会怎么做?

【问题讨论】:

  • 您的最后一个选项“使用模板化工作者”将是最好和最优雅的解决方案。它还具有性能最佳的优势,因为它在比较时避免了虚拟调用。
  • 与您的问题无关,但您确实不应该使用以下划线开头的名称。少数合法案件的规则非常复杂。前导下划线后跟大写字母或双前导下划线始终由实现保留。前导下划线后跟小写在全局命名空间中保留(因此在技术上是合法的,但养成不使用前导下划线的习惯会省去很多麻烦)
  • 这是一个非常相关的问题:似乎只有少数人意识到平等并不那么简单。

标签: c++ api inheritance casting


【解决方案1】:
  • 如果 Worker 在 Item 上被模板化而不是依赖继承不是更好吗?一般来说,价值语义(平等是价值语义的基础)不适用于继承。在您的具体示例中,如果 SomeContainer 确实复制了它存储在其中的内容,我也担心会被截断。

  • 如果你真的需要一个依赖于两个对象的动态类型的操作,你应该搜索“multiple dispatch”(或者查看“Modern C++ Design”,它有一个关于这个主题的章节)。有几种已知的技术可用于不同的权衡。最著名的一种通常与访问者模式相关联。最简单的变体取决于事先知道 Item 的所有后代。 (如果您查看访问者模式的描述,请注意该模式的两个层次结构将统一用于您的应用程序。

编辑:这是一个示例的草图:

class ItemA;
class ItemB;

class Item
{
public:
    virtual bool equal(Item const&) const = 0;
protected:
    virtual bool doesEqual(ItemA const*) const { return false; }
    virtual bool doesEqual(ItemB const*) const { return false; }
};

class ItemA: public Item
{
public:
    virtual bool equal(Item const& other) const
    {
        return other.doesEqual(this);
    }
protected:
    virtual bool doesEqual(ItemA const* other) {
        return do_the_check;
    }
};

class ItemB: public Item
{
public:
    virtual bool equal(Item const& other) const
    {
        return other.doesEqual(this);
    }
protected:
    virtual bool doesEqual(ItemB const* other) {
        return do_the_check;
    }
};

bool operator==(Item const& lhs, Item const& rhs)
{
    return lhs.equal(rhs);
}

【讨论】:

  • 好的,这是一个很好的折衷方案,因为尽管库需要了解类,但可以使用该库在特定应用程序中实现它们。关于复制的好点!我猜 Item 类将需要虚拟克隆功能,以及虚拟析构函数。
【解决方案2】:

恕我直言,平等取决于上下文。这意味着:环境 A 会认为两个对象在与环境 B 完全不同的基础上相等。

因此,我认为环境应该提供平等功能。

等式的另一个方面是它通常被认为是一个对称运算:如果a等于b,b等于a。这意味着它不能是单次调度的,即作为类方法实现。因此,它只需要依赖于两个对象的接口。这也强制使用环境提供的显式、双参数相等函数。

【讨论】:

  • 完全正确,相等性测试的实现是非常不同的。所以你说每个特定环境中的任何类之外都应该有一个相等函数?如果将它作为函数指针提供给 Worker 对象,那么 Worker 仍然需要知道函数签名。因为它只知道 Item 类,所以参数只能是 Item 类型。所以在相等函数内部,需要将项目转换为正确的类型,问题本质上是一样的,不是吗?
  • 确实,相等函数可能包含一个 bool (*eq)( Item,Item ) 接口。虽然我可以想象一个系统,其中环境要求使用更具体的类型安全相等函数的“类型化” Worker 对象(例如,使用模板成员函数)。
猜你喜欢
  • 1970-01-01
  • 2021-09-28
  • 1970-01-01
  • 2012-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-04
相关资源
最近更新 更多