C ++技术:类型擦除与纯多态性(C++ Techniques: Type-Erasure vs.

2019-08-02 13:07发布

什么是在比较两种技术的优点/缺点是什么? 更重要的是:为什么以及何时应该被一个比其他使用? 难道仅仅是一种个人品位/偏好的事?

要尽我的能力,我还没有发现在另一个帖子里明确地解决我的问题。 关于所实际使用的多态性和/或类型擦除的许多问题,下面似乎是最接近的,至少看起来是这样,但它并没有真正解决我的问题之一:

C ++ - &CRTP。 类型擦除VS多态性

请注意,我很好地理解这两种技术。 为此,我提供了一个简单,独立的,工作下面的例子,我很高兴地取出,如果觉得没有必要。 然而,例如应该澄清一下这两种技术对于我的问题的意思。 我没有兴趣讨论的命名。 另外,我知道和编译期运行时多态性之间的差异,但我不认为这是相关的问题。 请注意,我的兴趣是少性能差异,但如果有任何。 但是,如果有一个或基于绩效的另外一个惊人的说法,我很好奇阅读。 我特别想听听具体的例子(没有代码),将真的只能用这两种方法的工作之一。

寻找下面的例子中,一个主要的区别是内存管理,这对于多态保留在用户侧,以及用于类型擦除被整齐地卷起需要一些引用计数(或增压)。 话虽如此,这取决于使用情景,这种情况可能是多态性,例如通过使用智能指针与向量改善(?),但对于任意情况下,这很可能变成是不切实际的(?)。 另一方面,可能有利于类型擦除的,可能是一个通用接口的独立性,但到底为什么会是这样一个优势(?)。

如下面给出的代码通过简单地把所有的下面的代码块中的到单个源文件进行了测试用的VisualStudio MS 2008(编译&运行)。 还应该用gcc编译的Linux,或者我希望如此/假设,因为我看不出有什么理由,为什么不(?):-)我已经分手/分在这里的代码清晰。

这些报头文件应该是足够的,正确的(?)。

#include <iostream>
#include <vector>
#include <string>

简单的引用计数,以避免刺激(或其他方式)的依赖。 这个类仅在下面类型擦除-实施例中使用。

class RefCount
{
  RefCount( const RefCount& );
  RefCount& operator= ( const RefCount& );
  int m_refCount;

  public:
    RefCount() : m_refCount(1) {}
    void Increment() { ++m_refCount; }
    int Decrement() { return --m_refCount; }
};

这是简单的类型擦除示例/图示。 它被复制和部分来自下面的文章进行修改。 主要是我试图让它清晰和简单越好。 http://www.cplusplus.com/articles/oz18T05o/

class Object {
  struct ObjectInterface {
    virtual ~ObjectInterface() {}
    virtual std::string GetSomeText() const = 0;
  };

  template< typename T > struct ObjectModel : ObjectInterface {
    ObjectModel( const T& t ) : m_object( t ) {}
    virtual ~ObjectModel() {}
    virtual std::string GetSomeText() const { return m_object.GetSomeText(); }
    T m_object;
 };

  void DecrementRefCount() {
    if( mp_refCount->Decrement()==0 ) {
      delete mp_refCount; delete mp_objectInterface;
      mp_refCount = NULL; mp_objectInterface = NULL;
    }
  }

  Object& operator= ( const Object& );
  ObjectInterface *mp_objectInterface;
  RefCount *mp_refCount;

  public:
    template< typename T > Object( const T& obj )
      : mp_objectInterface( new ObjectModel<T>( obj ) ), mp_refCount( new RefCount ) {}
    ~Object() { DecrementRefCount(); }

    std::string GetSomeText() const { return mp_objectInterface->GetSomeText(); }

    Object( const Object &obj ) {
      obj.mp_refCount->Increment(); mp_refCount = obj.mp_refCount;
      mp_objectInterface = obj.mp_objectInterface;
    }
};

struct MyObject1 { std::string GetSomeText() const { return "MyObject1"; } };
struct MyObject2 { std::string GetSomeText() const { return "MyObject2"; } };

void UseTypeErasure() {
  typedef std::vector<Object> ObjVect;
  typedef ObjVect::const_iterator ObjVectIter;

  ObjVect objVect;
  objVect.push_back( Object( MyObject1() ) );
  objVect.push_back( Object( MyObject2() ) );

  for( ObjVectIter iter = objVect.begin(); iter != objVect.end(); ++iter )
    std::cout << iter->GetSomeText();
}

就我而言,这似乎达到几乎是使用多态相同,或者也许不是(?)。

struct ObjectInterface {
  virtual ~ObjectInterface() {}
  virtual std::string GetSomeText() const = 0;
};

struct MyObject3 : public ObjectInterface {
  std::string GetSomeText() const { return "MyObject3"; } };

struct MyObject4 : public ObjectInterface {
  std::string GetSomeText() const { return "MyObject4"; } };

void UsePolymorphism() {
  typedef std::vector<ObjectInterface*> ObjVect;
  typedef ObjVect::const_iterator ObjVectIter;

  ObjVect objVect;
  objVect.push_back( new MyObject3 );
  objVect.push_back( new MyObject4 );

  for( ObjVectIter iter = objVect.begin(); iter != objVect.end(); ++iter )
    std::cout << (*iter)->GetSomeText();

  for( ObjVectIter iter = objVect.begin(); iter != objVect.end(); ++iter )
    delete *iter;
}

最后一起为测试上述所有。

int main() {
  UseTypeErasure();
  UsePolymorphism();
  return(0);
}

Answer 1:

基于C ++式虚拟方法多态性

  1. 你必须使用类来保存数据。
  2. 每个类都有与牢记您的特定种类的多态性进行建造。
  3. 每个类都有一个共同的二进制级别的依赖,这限制了编译器如何创建每个类的实例。
  4. 你是抽象的数据必须明确说明,描述你的需求的接口。

C ++样式模板基于类型擦除(用虚方法基于多态性做擦除):

  1. 你必须使用模板来谈谈您的数据。
  2. 您正在使用的每个数据块可能是完全无关的其他选项。
  3. 该类型擦除工作是公共的头文件中完成的,其腌编译时间。
  4. 删除每种类型都有其自己的模板实例,可臃肿二进制文件的大小。
  5. 你提取所需的数据,不能写成是直接依赖于你的需求。

现在,哪个更好? 嗯,这取决于如果上面的东西都在你的特殊情况的好坏。

作为一个明确的实例, std::function<...>使用类型擦除这使得它能够采取函数指针,函数引用,一个整体的桩产生类型在编译时基于模板的功能,其具有仿函数myraids的输出操作者()和lambda表达式。 所有这些类型都是互不相干。 并且因为它们不依赖于具有virtual operator()当它们的外使用std::function上下文其所代表的抽象可编译程。 无类型擦除你不能做到这一点,你可能不会想。

在另一方面,仅仅因为一个类有一个方法DoFoo ,并不意味着他们都做同样的事情。 随着多态性,它不只是任何DoFoo你打电话,但DoFoo从一个特定的接口。

至于你的示例代码...你GetSomeText应该是virtual ... override的多态性情况。

有没有必要引用计数只是因为你使用的类型擦除。 有没有必要不只是因为你正在使用polymorphsm使用引用计数。

你的Object可以换T*就像你如何存储vector裸指针第在另一种情况下,这些文件的内容手动破坏(相当于不必调用删除)。 你的Object可以换一个std::shared_ptr<T>而在其他情况下,你可以有vectorstd::shared_ptr<T> 您的Object可能包含std::unique_ptr<T>等价于具有的向量std::unique_ptr<T>在另一种情况下。 你的ObjectObjectModel可以从提取的拷贝构造函数和赋值操作符T ,揭露他们Object ,从而为您全方位价值的语义Object ,对应于一个vectorT你的多态性情况。



Answer 2:

这里有一个观点:这个问题似乎问一个应该如何后期绑定之间进行选择(“运行时多态性”)和早期绑定(“编译时多态性”)。

作为KerrekSB在他的评论中指出,有一些事情你可以用后期绑定,这恰恰是不现实的早期绑定去做做。 策略模式(解码网络I / O)或抽象工厂模式(运行时选择的类工厂)的许多用途属于这一类。

如果这两种方法都是可行的,然后选择是所涉及的权衡的问题。 在C ++应用程序,我早和后期绑定之间看到主要的权衡是实现可维护性,二进制文件的大小和性能。

至少有一些人谁觉得C ++中的任何形状或形式的模板是无法理解。 或者可能有模板一些其他的,不太引人注目预订。 C ++模板有很多小陷阱(“做,当我需要使用‘类型名称’和‘模板’的关键字?”),和非显而易见的技巧(SFINAE想到)。

另一个代价是优化。 当您绑定的早期,你给编译器的更多信息,你的程序,因此它可以(可能)做得更好优化。 当您绑定晚,编译器(可能)不知道提前尽可能多的信息 - 其中的一些信息可能是在其他编译单元,所以优化器不能做多。

另一个代价是程序大小。 在C ++中至少使用“编译时多态性”有时气球二进制大小,如编译器创建,优化,并发出不同的码用于每个使用专业化。 相反,结合后期的时候,只有一个代码路径。

有趣的是比较在不同的上下文由相同的权衡。 以Web应用程序,其中一个用途(某种类型的)多态性来处理浏览器之间的差异,并可能对国际化(i18n)/定位。 现在,手写JavaScript的Web应用程序可能会使用数额是多少,后期绑定在这里,由具有在运行时检测功能,以找出要做什么方法。 像jQuery库采取这种策略。

另一种方法是写出不同的代码对每个可能的浏览器/ I18N可能性。 虽然这听起来很荒谬,这是远远闻所未闻。 谷歌网页工具包使用了这种方法。 GWT有它的“延迟绑定”的机制,用来编译器的输出专注于不同浏览器和本地化。 GWT的“延迟绑定”机制使用早期绑定:GWT Java到JavaScript编译器计算出所有可能的方式可能需要的多态性,并吐出了一个完全不同的“二进制”每个。

权衡是相似的。 包裹你的头围绕如何您在使用延迟绑定可有头痛半延长GWT; 在编译时有知识允许GWT的编译器分别优化每一个专业化,有可能产生更好的性能,并为每个专业化更小的尺寸; GWT应用程序的整体能最终成为一个可比的jQuery应用程序的大小很多次,由于所有的预编译的专业领域。



Answer 3:

一个好处运行时,在这里没有人提到泛型(?)是生成并注入到正在运行的应用程序的代码的可能性,使用相同的ListHashmap / Dictionary等其他一切在应用程序已经使用。 为什么你要做到这一点,则是另一个问题。



文章来源: C++ Techniques: Type-Erasure vs. Pure Polymorphism
标签: c++ paradigms