C++ - Type Erasure

Key points

继承中的多态:子类实现(implement)父类中定义的接口。

模板中的多态:不同的类遵守(conform to)相同的接口。

使用基于模板的多态的问题是caller也要是模板,即传染性。总的来说,有需要异质(heterogeneous)容器的需求;而一般的容器都要求元素是相同的类型(homogeneous)。

OO中的interface类比于template中的concept;OO中的interface和implement/instance类比于concept和model。

std::any是一个类(不是类模板),以type-safe的方式存is_copy_constructible类型的值。即只要类型conform to is_copy_constructible接口,它的值就能存在any对象里面。any擦除了任何is_copy_constructible类型的实际类型。

std::function是个类模板,它是一个function wrapper,可以存并调用任何CopyConstructibleCallable的target:function pointer,lambda expression,bind expression,function object/functor,pointer to member function(non-static/static,virtual/non-virtual),pointer to data member。function擦除了target的实际类型,而其模板参数表明了target应该conform to的函数原型。

尽管etl::delegate看上去和std::function很像,但是它没有使用类型擦除技术;是否有类型擦除,主要还是看它是存wrapper还是不是。

和CRTP的不同

Type Erasure实现了runtime polymorphism(但不依靠继承),CRTP(Curiously Recurring Template Pattern)实现了compile-time polymorphism(static dispatch或者policy-based design)。Type Erasure以性能换取弹性(trades performance for flexibility),CRTP以弹性换取性能(trades flexibility for performance),更强的type safety。

简单从表相上来看,Type Erasure是模板类继承自普通类,CRTP是普通类继承自模板类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Type Erasure

class Concept {
public:
virtual void interface() = 0;
};

template<typename Concrete>
class Model : public Concept {
Concrete* object; // hold the concrete object
public:
virtual void interface() override {
// dispatch to object->implementation()
}
};

class Concrete {
void implementation() {} // don't have to be virtual
};
1
2
3
4
5
6
7
8
9
10
// CRTP

template<typename Derived>
struct Base {
void interface() { static_cast<Derived*>(this)->implementation(); }
};

struct Derived : public Base<Derived> {
void implementation() {}
};

Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class SeeAndSay {
class AnimalConcept {
public:
virtual const char* see() const = 0;
virtual const char* say() const = 0;
};

template<typename T>
class AnimalModel : public AnimalConcept {
const T* m_animal;
public:
AnimalModel(const T* animal) : m_animal(animal) {}
const char* see() const { return m_animal->see(); }
const char* say() const { return m_animal->say(); }
};

vector<AnimalConcept*> m_animals;

public:
template<typename T>
void addAnimal(T* animal) {
m_animals.push_back(new AnimalModel(animal));
}

void pullTheString() {
for (auto animal : m_animals) {
cout << "The " << animal->see()
<< " says '" << animal->say()
<< "'!" << endl;
}
}
};

struct Cow {
const char* see() const { return "cow"; }
const char* say() const { return "moo"; }
};

struct Pig {
const char* see() const { return "pig"; }
const char* say() const { return "oink"; }
};

struct Dog {
const char* see() const { return "dog"; }
const char* say() const { return "woof"; }
};

int main() {
SeeAndSay seeAndSay;
seeAndSay.addAnimal(new Cow);
seeAndSay.addAnimal(new Pig);
seeAndSay.addAnimal(new Dog);
seeAndSay.pullTheString();

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Object {
struct ObjectConcept {
virtual ~ObjectConcept() = default;
};

template<typename T>
struct ObjectModel : ObjectConcept {
ObjectModel(const T& t) : object(t) {}
virtual ~ObjectModel() = default;
private:
T object;
};

shared_ptr<ObjectConcept> object;

public:
template<typename T>
Object(const T& t) : object(make_shared<ObjectModel<T>>(t)) {}
};

struct Foo {};
struct Bar {};

int main() {
vector<Object> objects;

objects.push_back(Object(1));
objects.push_back(Object(Foo()));
objects.push_back(Object(Bar()));

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Greeter {
public:
template<class T>
Greeter(T data) : self_(make_shared<Model<T>>(data)) {}

void greet(const string& name) const {
self_->greet(name);
}

private:
struct Concept {
virtual ~Concept() = default;
virtual void greet(const string&) const = 0;
};

template<class T>
class Model : public Concept {
public:
Model(T data) : data_(data) {}

virtual void greet(const string& name) const override {
data_.greet(name);
}

private:
T data_;
};

shared_ptr<const Concept> self_;
};

struct English {
void greet(const string& name) const {
cout << "Good day " << name << ". How are you?\n";
}
};

struct French {
void greet(const string& name) const {
cout << "Bonjour " << name << ". Comment ca va?\n";
}
};

void greet_tom(const Greeter& g) {
g.greet("Tom");
}

int main() {
greet_tom(English());
greet_tom(French());

return 0;
}

References