Extreme type erasure via std::function 通过模板(而不是继承)实现接口
2017-04-26 15:19
681 查看
转自: https://a4z.bitbucket.io/blog/2017/01/11/exterm_typererasure-using-std::function.html
本文:
1.介绍了一种通过模板(而不是继承)实现接口的方法,并且可以将不同类型(具有接口指定方法)的对象放入同一个容器。
2.介绍了通过std::function对可调用对象进行类型擦除。
If you have a good title add some content… so I did and you can read the result here ;-)
Some time ago, I think it was 2013, Sean Parent gave a talk where he demonstrated how to use type erasure to avoid implementing interfaces in classes.
The talk was called “Inheritance is the base class of evil” and you can find it online. If you have not already seen it you should watch it.
This summary is maybe to short and handles not all aspects, but that’s OK. I do not what to handle all aspects but rather keep it simple.
You have different type of classes, like TextWidget, BoxWedget, ….
You want to have instances of those types in a list
You want to call a method, like for example
The common way to implement this is having a IDrawable interface with a draw method and each class needs to implement this interface. Than you have a bunch of 'drawable' objects and have a list of them, for example in a
Sean Parent showed that there is a different way to do this.
After seeing this way my reaction was something of:
What ??? hmmm…. ??? … ahhh …, of course!
Why did I not think about that myself, why did nobody tell me already years ago….
You might know such moments.
A incomplete and to short summary
It is possible to create a drawable_type that can hold different types of classes.
This type has a draw method and calls the draw method of the class it hold.
The drawable_type creates therefor an interface and hides the concrete type behind that.
This is done with the help of templates.
This sounds complicated, but becomes pretty obvious if you see it in code.
So here a short sample implementation that you can copy and use to play with.
You can compile the above code, works also with older compilers like gcc 4.8.x
Running the code will, less surprising, look like that
So far so good, you did possible already know about that, if not, I hope this summary helped you to understand what this type erasure thing is about.
Now, to the topic, extreme type erasure with std::function.
I call it like that because I had not other one-liner and it sounds like a good headline.
I recently had something like 'sendable' objects but they did not have anything in common.
No same named function implemented, and some of the objects where even functions them self!
But I needed dynamic assembled lists of all these things that send something and call them in order as they are in the list.
To make it as short as possible, the solution I have chosen looks something like that:
And then filling
Yes, of course!
This is super short, super flexible and super simple, isn’t it?
Via std::function I can put anything in the call list, no matter if it makes quack, muhh or määä.
Of course the signature of the function object in the vector might vary, also what type of callable objects are in there and what they capture. And in reality it does.
But no need to keep this post not as simple as possible.
I think we all got the point that this is just an other of those "of course you can do it like that" things.
But it took me a moment to realize that this is also type erasure, just more extreme, and therefore the title of this post.
And just to see that what I am talking about also compiles, here a short sample:
That’s it, extreme type erasure via std::function. :-)
本文:
1.介绍了一种通过模板(而不是继承)实现接口的方法,并且可以将不同类型(具有接口指定方法)的对象放入同一个容器。
2.介绍了通过std::function对可调用对象进行类型擦除。
If you have a good title add some content… so I did and you can read the result here ;-)
Some time ago, I think it was 2013, Sean Parent gave a talk where he demonstrated how to use type erasure to avoid implementing interfaces in classes.
The talk was called “Inheritance is the base class of evil” and you can find it online. If you have not already seen it you should watch it.
Summarize type erasure
This summary is maybe to short and handles not all aspects, but that’s OK. I do not what to handle all aspects but rather keep it simple.
A classical way
You have different type of classes, like TextWidget, BoxWedget, ….You want to have instances of those types in a list
You want to call a method, like for example
draw, on each of the objects in the list
The common way to implement this is having a IDrawable interface with a draw method and each class needs to implement this interface. Than you have a bunch of 'drawable' objects and have a list of them, for example in a
std::vector<IDrawable*>.
A different way
Sean Parent showed that there is a different way to do this.After seeing this way my reaction was something of:
What ??? hmmm…. ??? … ahhh …, of course!
Why did I not think about that myself, why did nobody tell me already years ago….
You might know such moments.
A incomplete and to short summary
It is possible to create a drawable_type that can hold different types of classes.
This type has a draw method and calls the draw method of the class it hold.
The drawable_type creates therefor an interface and hides the concrete type behind that.
This is done with the help of templates.
This sounds complicated, but becomes pretty obvious if you see it in code.
So here a short sample implementation that you can copy and use to play with.
#include <iostream> #include <vector> #include <memory> // 2 classes, class1 and class2, // both have a draw method, but thats all they have in common. // we want to have class1 and class2 in a list, // they do not share a type, struct class1 { void draw() const { std::cout << "class1" << std::endl; } }; struct class2 { void draw() const { std::cout << "class2" << std::endl; } }; // so we create a drawable type class drawable_t { // drawable knows the concept of drawing objects, // using a simple abstract class as you know it struct concept_t { virtual ~concept_t() = default ; virtual void draw() const = 0; }; // A model realize the concept by inheriting it // What it actually calls is intern and private // This works as long as: // 1) the given object is movable // 2) the given object has a draw method template< typename T > struct model_t : concept_t { // constructing the model model_t(T x) : erased_type(std::move(x) ) {} // call draw on whatever it holds void draw() const { erased_type.draw(); } // Whatever it holds member variable // Some user spcified type what is erased now. // We only know it has a draw method, // if not, a a compiler error happens. T erased_type; }; // The drawable type has to hold the model. // No problem, we can do that via the interface. std::unique_ptr<const concept_t> self_; public: // The constructor is a template, // we don't know what type will be used. // It has to work with // every movable type that has a draw method. template< typename T > drawable_t(T x) : self_(new model_t<T> (std::move(x))) {} // the draw call void draw() const { self_->draw(); } }; // drawable_t hold model_t<SomeType> // the requirement of some type is to have a draw method. // has a pointer to the abstract class concept_t, // What concept_t pointer points to is a concrete model_t<SomeType>, // some type is hidden // we do not know about it when we write drawable_t. // It is a bit like duck typing. // If we would use quack instead of draw as the required method // and call our drawable_t duck. // We could hide everything that has a quack method inside a duck. // usage of the code above works like that int main(int argc, char* argv[]) { std::vector<drawable_t> objects; // add whatever movable object that has a draw method objects.emplace_back(class1()); objects.emplace_back(class2()); // draw all the different typs for (const auto& object : objects) object.draw(); return 0; }
You can compile the above code, works also with older compilers like gcc 4.8.x
g++ -std=c++11 sample.cpp
Running the code will, less surprising, look like that
./a.out class1 class2
So far so good, you did possible already know about that, if not, I hope this summary helped you to understand what this type erasure thing is about.
Extreme type erasure with std::function
Now, to the topic, extreme type erasure with std::function.I call it like that because I had not other one-liner and it sounds like a good headline.
I recently had something like 'sendable' objects but they did not have anything in common.
No same named function implemented, and some of the objects where even functions them self!
But I needed dynamic assembled lists of all these things that send something and call them in order as they are in the list.
To make it as short as possible, the solution I have chosen looks something like that:
std::vector<std::function<void()>> senderlist ;
And then filling
senderlistit with lambdas that capture what ever they do and need.
And that’s it?
Yes, of course!This is super short, super flexible and super simple, isn’t it?
Via std::function I can put anything in the call list, no matter if it makes quack, muhh or määä.
Of course the signature of the function object in the vector might vary, also what type of callable objects are in there and what they capture. And in reality it does.
But no need to keep this post not as simple as possible.
I think we all got the point that this is just an other of those "of course you can do it like that" things.
But it took me a moment to realize that this is also type erasure, just more extreme, and therefore the title of this post.
The example
And just to see that what I am talking about also compiles, here a short sample:#include <iostream> #include <vector> #include <functional> void sendto(int dest, int data) { std::cout << "sendto" << dest << ": " << data << std::endl ; } struct Foo{ void send_1(int data) { std::cout << "send foo 1: " << data << std::endl; } void send_2(int data){ std::cout << "send foo 2: " << data << std::endl; } }; struct Bar{ int data ; void send() { std::cout << "send bar " << data <<std::endl; } }; int main(int argc, char* argv[]) { std::vector<std::function<void()>> sender; int data = 12 ; sender.emplace_back([data]{sendto(0,data);}) ; Foo f; data = 34; sender.emplace_back([&f, data]() { f.send_1(data) ; }) ; data = 56; sender.emplace_back([&f, data]() { f.send_2(data) ; }) ; Bar b{78}; sender.emplace_back([&b]() { b.send() ; }) ; for (auto& send : sender) send(); return 0; } // compile with g++ std=c++11 .. // running will print // sendto0: 12 // send foo 1: 34 // send foo 2: 56 // send bar 78
That’s it, extreme type erasure via std::function. :-)
相关文章推荐
- 改进C#代码之22:通过定义并实现接口替代继承
- 启动线程的五种方式方法(通过继承Thread类或实现Runnable接口)
- 对比:通过实现Runnable接口和继承thread类来生成多线程
- PHP Smarty模板继承。 通过内置函数extends和block实现父模板(视图母版页面和内容页)
- 改进C#代码之24:通过定义并实现接口替代继承
- 模板基链的实现,实现c#的接口,单继承模式
- 通过继承Thread实现多继承和通过实现Runnable接口实现多线程的比较
- 通过继承Thread实现多继承和通过实现Runnable接口实现多线程的比较
- 通过继承UBlueprintFunctionLibrary的类实现蓝图与C++相互通信
- java中的通过继承Thread和实现Runnable接口实现的线程
- C++ - 通过私有(private)继承复用类实现工厂函数(factory function) 详解
- java多线程选择实现Runnable接口而不是直接继承Thread类的原因
- 通过继承Thread类和通过实现Runnable接口 创建线程的区别
- [function & type_traits] VC6 的 function traits 和 function 模板的新的实现方法
- 19.选择定义和实现接口,而不是继承
- 桥接模式(把接口和实现分为两个继承树,而不是将实现来继承接口,造成实现和接口耦合
- 使用boost::enable_if与boost::type_traits在模板中实现对模板类型的接口的限制
- 使用继承或接口实现模板方法的方式与函数回调的感悟
- Effective C# 原则19:选择定义和实现接口而不是继承(译)
- EffectiveC#00--选择定义和实现接口,而不是继承