C ++技术:类型擦除与纯多态性 [英] C++ Techniques: Type-Erasure vs. Pure Polymorphism

查看:113
本文介绍了C ++技术:类型擦除与纯多态性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这两种技术的优点/缺点是什么?更重要的是:为什么和什么时候应该使用另一个?这只是个人品味/偏好的问题吗?



为了我的能力,我没有找到另一篇明确地解决我的问题的帖子。在关于多态性和/或类型擦除的实际使用的许多问题中,以下似乎是最接近的,或者它似乎,但它不真正解决我的问题:



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



请注意,我非常了解这两种技术。为此,我在下面提供了一个简单,自包含的工作示例,如果觉得不必要,我很乐意删除。但是,示例应该澄清这两种技术对我的问题的意思。我对讨论命名不感兴趣。此外,我知道编译和运行时多态性之间的区别,虽然我不认为这是与问题相关。注意,我的兴趣在性能差异,如果有的话。然而,如果一个或另一个基于表演有一个引人注目的争论,我会很想读它。特别是,我想听听具体的例子(没有代码),真正只能使用两种方法之一。



看下面的例子,一个主要差异是内存管理,其对于多态性保持在用户侧,并且对于类型擦除整齐地卷起,需要一些引用计数(或提升)。尽管如此,根据使用场景,可以通过使用具有向量(θ)的智能指针来改善多态性的情况,但是对于任意情况,这可能非常好地证明是不切实际的(?)。另一个方面,可能有利于类型擦除,可能是一个通用接口的独立性,但为什么会是一个优势(?)。



通过简单地将所有以下代码块放入单个源文件中来测试(编译和运行)MS VisualStudio 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<类型名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;删除mp_objectInterface;
mp_refCount = NULL; mp_objectInterface = NULL;
}
}

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

public:
template<类型名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 {returnMyObject1; }};
struct MyObject2 {std :: string GetSomeText()const {returnMyObject2; }};

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 $ b virtual std :: string GetSomeText()const = 0;
};

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

结构MyObject4:public ObjectInterface {
std :: string GetSomeText()const {returnMyObject4; }};

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);
}


解决方案

C ++ style基于虚拟方法的多态:


  1. 您必须使用课程来保存您的资料。


  2. 每个类都有一个通用的二进制级依赖,它限制
    编译器如何创建每个类的实例。

  3. 您抽象的数据必须明确描述描述您需要的
    的接口。

C ++基于类型模板的类型擦除(使用基于虚拟方法的多态性执行擦除):


  1. 您必须使用模板来谈论您的数据。 / li>
  2. 您正在处理的每个数据块可能与其他选项完全无关。

  3. 类型擦除工作是在公共头文件中完成的,

  4. 每个已擦除的类型都有自己的模板实例化,这会导致二进制大小膨胀。

  5. 您抽象的数据不需要写为


  6. 现在,这更好吗?



    作为一个明确的例子, std :: function< .. 。> 使用类型擦除,它允许它采用函数指针,函数引用,输出一堆基于模板的函数在编译时生成类型,myraids的函子有操作符() ,和lambdas。所有这些类型彼此无关。并且因为它们不受限于 virtual operator(),当它们在 std :: function context它们表示的抽象可以被编译掉。你不能这样做没有类型擦除,你可能不想要。



    另一方面,只是因为一个类有一个方法 DoFoo ,并不意味着他们都做同样的事情。对于多态性,不仅仅是任何 DoFoo 正在调用,而是来自特定接口的 DoFoo p>

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



    没有必要引用计数,因为你使用类型擦除。



    您的对象可以换行 T * 就像你在其他情况下存储向量的原始指针,手动销毁它们的内容不得不调用删除)。您的对象可以包装 std :: shared_ptr< T> ,在其他情况下, $ c> std :: shared_ptr< T> 的向量。您的对象可能包含 std :: unique_ptr< T> ,等效于向量 std :: unique_ptr< T> 。您的对象 ObjectModel 可以从 T 并将它们暴露给 Object ,允许您的对象具有完全开启的值语义,

    $

    What are the advantages/disadvantages of the two techniques in comparison ? And more importantly: Why and when should one be used over the other ? Is it just a matter of personal taste/preference ?

    To the best of my abilities, I haven't found another post that explicitly addresses my question. Among many questions regarding the actual use of polymorphism and/or type-erasure, the following seems to be closest, or so it seemed, but it doesn't really address my question either:

    C++ -& CRTP . Type erasure vs polymorphism

    Please, note that I very well understand both techniques. To this end, I provide a simple, self-contained, working example below, which I'm happy to remove, if it is felt unnecessary. However, the example should clarify what the two techniques mean with respect to my question. I'm not interested in discussing nomenclatures. Also, I know the difference between compile- and run-time polymorphism, though I wouldn't consider this to be relevant to the question. Note that my interest is less in performance-differences, if there are any. However, if there was a striking argument for one or the other based on performance, I'd be curious to read it. In particular, I would like to hear about concrete examples (no code) that would really only work with one of the two approaches.

    Looking at the example below, one primary difference is the memory-management, which for polymorphism remains on the user-side, and for type-erasure is neatly tucked away requiring some reference-counting (or boost). Having said that, depending on the usage scenarios, the situation might be improved for the polymorphism-example by using smart-pointers with the vector (?), though for arbitrary cases this may very well turn out to be impractical (?). Another aspect, potentially in favor of type-erasure, may be the independence of a common interface, but why exactly would that be an advantage (?).

    The code as given below was tested (compiled & run) with MS VisualStudio 2008 by simply putting all of the following code-blocks into a single source-file. It should also compile with gcc on Linux, or so I hope/assume, because I see no reason why not (?) :-) I have split/divided the code here for clarity.

    These header-files should be sufficient, right (?).

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

    Simple reference-counting to avoid boost (or other) dependencies. This class is only used in the type-erasure-example below.

    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; }
    };
    

    This is the simple type-erasure example/illustration. It was copied and modified in part from the following article. Mainly I have tried to make it as clear and straightforward as possible. 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();
    }
    

    As far as I'm concerned, this seems to achieve pretty much the same using polymorphism, or maybe not (?).

    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;
    }
    

    And finally for testing all of the above together.

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

    解决方案

    C++ style virtual method based polymorphism:

    1. You have to use classes to hold your data.
    2. Every class has to be built with your particular kind of polymorphism in mind.
    3. Every class has a common binary-level dependency, which restricts how the compiler creates the instance of each class.
    4. The data you are abstracting must explicitly describe an interface that describes your needs.

    C++ style template based type erasure (with virtual method based polymorphism doing the erasure):

    1. You have to use template to talk about your data.
    2. Each chunk of data you are working on may be completely unrelated to other options.
    3. The type erasure work is done within public header files, which bloats compile time.
    4. Each type erased has its own template instantiated, which can bloat binary size.
    5. The data you are abstracting need not be written as being directly dependent on your needs.

    Now, which is better? Well, that depends if the above things are good or bad in your particular situation.

    As an explicit example, std::function<...> uses type erasure which allows it to take function pointers, function references, output of a whole pile of template-based functions that generate types at compile time, myraids of functors which have an operator(), and lambdas. All of these types are unrelated to one another. And because they aren't tied to having a virtual operator(), when they are used outside of the std::function context the abstraction they represent can be compiled away. You couldn't do this without type erasure, and you probably wouldn't want to.

    On the other hand, just because a class has a method called DoFoo, doesn't mean that they all do the same thing. With polymorphism, it isn't just any DoFoo you are calling, but the DoFoo from a particular interface.

    As for your sample code... your GetSomeText should be virtual ... override in the polymorphism case.

    There is no need to reference count just because you are using type erasure. There is no need not to use reference counting just because you are using polymorphsm.

    Your Object could wrap T*s like how you stored vectors of raw pointers in the other case, with manual destruction of their contents (equivalent to having to call delete). Your Object could wrap a std::shared_ptr<T>, and in the other case you could have vector of std::shared_ptr<T>. Your Object could contain a std::unique_ptr<T>, equivalent to having a vector of std::unique_ptr<T> in the other case. Your Object's ObjectModel could extract copy constructors and assignment operators from the T and expose them to Object, allowing full-on value semantics for your Object, which corresponds to the a vector of T in your polymorphism case.

    这篇关于C ++技术:类型擦除与纯多态性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆