我需要存储一个指向对象的指针容器。 这些对象有一些常见的方法/属性(接口),我想强制执行(可能在编译时)和使用。 例如:
struct A{
void fly(){}
};
struct B{
void fly(){}
};
A a;
B b;
std::vector<some *> objects;
objects.push_back(&a);
objects.push_back(&b);
for(auto & el: objects)
el->fly();
更简单的解决方案是A
和B
继承公共基类,如FlyingClass
:
struct FlyingClass{
void fly(){}
};
struct A: public FlyingClass { ...
struct B: public FlyingClass { ...
并创建一个
std::vector<FlyingClass *> objects;
这将有效并且还强制实施我只能添加objects
可以飞行的事物(实施FlyingClass
)。
但是如果我需要实现一些其他常见的方法/属性而不将它们与上面的基类耦合呢?
示例:
struct A{
void fly(){}
void swim(){}
};
struct B{
void fly(){}
void swim(){}
};
我想这样做:
for(auto & el: objects) {
el->fly();
...
el->swim();
...
}
一般来说,我可以调用一个传递这些指针之一的函数并访问常用的方法/属性,例如:
void dostuff(Element * el){
el->fly();
el->swim();
}
我可以尝试继承另一个界面,如:
struct SwimmingClass{
void swim(){}
};
struct A: public FlyingClass, public SwimmingClass { ...
struct B: public FlyingClass, public SwimmingClass { ...
但那么容器应该包含什么?
std::vector<FlyingClass&&SwimmingClass *> objects;
当然,我可以实现SwimmingFlyingClass
,但如果我需要RunningClass
等,那该怎么办呢。这将是一场噩梦。
换句话说,如何在不耦合它们的情况下实现指向多个接口的指针?
还是有一些重新思考问题的模板方法? 如果有一种优雅且可维护的方式,即使是运行时类型信息也可以在我的应用程序中接受。
答案 0 :(得分:1)
它 可以这样做,以一种漂亮的TMP方式,在运行时有点贵。重新设计是有利的,因此不需要这样做。如果没有C ++不支持的语言支持,那么你想要做的就是干净利落。
至于丑陋,请保护你的眼睛:
struct AnyBase { virtual ~AnyBase() {} }; // All derived classes inherit from.
template<typename... T> class Limited {
AnyBase* object;
template<typename U> Limited(U* p) {
static_assert(all<is_base_of<T, U>...>::value, "Must derive from all of the interfaces.");
object = p;
}
template<typename U> U* get() {
static_assert(any<is_same<U, T>...>::value, "U must be one of the interfaces.");
return dynamic_cast<U*>(object);
}
}
其中一些东西没有被定义为标准,所以我只是通过它。构造函数上的static_assert
强制U
继承自T
的所有内容。我的U和T可能是错误的,而all
的定义留待读者阅读。
getter只需要U
是模板参数T...
之一。然后我们事先知道dynamic_cast
将成功,因为我们静态检查了约束。
这很难看,但应该有效。所以考虑一下
std::vector<Limited<Flying, Swimming>> objects;
for(auto&& obj : objects) {
obj.get<Flying>()->fly();
obj.get<Swimming>()->swim();
}
答案 1 :(得分:0)
你要求的东西一般没有意义,这就是为什么没有简单的方法去做。
您要求能够将异构对象存储在集合中,其界面甚至可以不同。
如何在不知道类型的情况下迭代集合?您被限制为最不具体或被迫做dynamic_cast
指针和交叉指针。
class Entity { }
class SwimmingEntity : public Entity {
virtual void swim() = 0;
}
class FlyingEntity : public Entity {
virtual void fly() = 0;
}
class Fish : public SwimmingEntity {
void swim() override { }
}
class Bird : public FlyingEntity {
void fly() override { }
}
std:vector<Entity*> entities;
这是合法的,但不会向您提供有关运行时Entity
实例功能的任何信息。除非你使用dynamic_cast
和rtti(或手动rtti)进行处理,否则它不会带来任何影响,那么哪里有优势呢?
答案 2 :(得分:0)
这几乎是一个需要type erasure的教科书示例。
我们的想法是定义一个内部抽象(纯虚拟)接口类,它捕获您想要的常见行为,然后使用模板化构造函数创建从该接口派生的代理对象:
#include <iostream>
#include <vector>
#include <memory>
using std::cout;
struct Bird {
void fly() { cout << "Bird flies\n"; }
void swim(){ cout << "Bird swims\n"; }
};
struct Pig {
void fly() { cout << "Pig flies!\n"; }
void swim() { cout << "Pig swims\n"; }
};
struct FlyingSwimmingThing {
// Pure virtual interface that knows how to fly() and how to swim(),
// but does not depend on type of underlying object.
struct InternalInterface {
virtual void fly() = 0;
virtual void swim() = 0;
virtual ~InternalInterface() { }
};
// Proxy inherits from interface; forwards to underlying object.
// Template class allows proxy type to depend on object type.
template<typename T>
struct InternalImplementation : public InternalInterface {
InternalImplementation(T &obj) : obj_(obj) { }
void fly() { obj_.fly(); }
void swim() { obj_.swim(); }
virtual ~InternalImplementation() { }
private:
T &obj_;
};
// Templated constructor
template<typename T>
FlyingSwimmingThing(T &obj) : proxy_(new InternalImplementation<T>(obj))
{ }
// Forward calls to underlying object via virtual interface.
void fly() { proxy_->fly(); }
void swim() { proxy_->swim(); }
private:
std::unique_ptr<InternalInterface> proxy_;
};
int main(int argc, char *argv[])
{
Bird a;
Pig b;
std::vector<FlyingSwimmingThing> objects;
objects.push_back(FlyingSwimmingThing(a));
objects.push_back(FlyingSwimmingThing(b));
objects[0].fly();
objects[1].fly();
objects[0].swim();
objects[1].swim();
}
同样的技巧用于the deleter in a shared_ptr
和std::function。后者可以说是该技术的典型代表。
你总会在那里找到一个“新”的电话。此外,如果您希望包装类保存底层对象的副本而不是指针,您会发现在抽象接口类中需要clone()
函数(其实现也将调用new
) 。所以这些东西很容易变得非常不具备性能,这取决于你在做什么......
[更新]
只是为了让我的假设清楚,因为有些人似乎没有读过这个问题......
您有多个类实现fly()
和swim()
函数,但这就是这些类的共同点;他们不继承任何常见的接口类。
目标是拥有一个包装器对象,该对象可以存储指向这些类中的任何一个的指针,并且通过它可以调用fly()
和swim()
函数,而无需知道调用时的包装类型现场。 (花些时间阅读问题以查看示例;例如搜索dostuff
。)此属性称为“封装”;也就是说,包装器直接公开fly()
和swim()
接口,它可以隐藏不相关的包装对象的任何属性。
最后,应该可以使用自己的fly()
和swim()
函数创建一个新的不相关的类,并让包装器保存指向该类的指针(a)而不修改包装类(b)不通过包装器接触fly()
或swim()
的任何电话。
正如我所说,这些是类型擦除的教科书特征。我没有发明这个成语,但我确实知道什么时候需要它。