C ++ - 通过单个指针访问多个对象的接口

时间:2014-07-26 14:56:52

标签: c++ oop templates interface polymorphism

我需要存储一个指向对象的指针容器。 这些对象有一些常见的方法/属性(接口),我想强制执行(可能在编译时)和使用。 例如:

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

更简单的解决方案是AB继承公共基类,如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等,那该怎么办呢。这将是一场噩梦。 换句话说,如何在不耦合它们的情况下实现指向多个接口的指针?

还是有一些重新思考问题的模板方法? 如果有一种优雅且可维护的方式,即使是运行时类型信息也可以在我的应用程序中接受。

3 个答案:

答案 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_ptrstd::function。后者可以说是该技术的典型代表。

你总会在那里找到一个“新”的电话。此外,如果您希望包装类保存底层对象的副本而不是指针,您会发现在抽象接口类中需要clone()函数(其实现也将调用new) 。所以这些东西很容易变得非常不具备性能,这取决于你在做什么......

[更新]

只是为了让我的假设清楚,因为有些人似乎没有读过这个问题......

您有多个类实现fly()swim()函数,但这就是这些类的共同点;他们继承任何常见的接口类。

目标是拥有一个包装器对象,该对象可以存储指向这些类中的任何一个的指针,并且通过它可以调用fly()swim()函数,而无需知道调用时的包装类型现场。 (花些时间阅读问题以查看示例;例如搜索dostuff。)此属性称为“封装”;也就是说,包装器直接公开fly()swim()接口,它可以隐藏不相关的包装对象的任何属性。

最后,应该可以使用自己的fly()swim()函数创建一个新的不相关的类,并让包装器保存指向该类的指针(a)而不修改包装类(b)不通过包装器接触fly()swim()的任何电话。

正如我所说,这些是类型擦除的教科书特征。我没有发明这个成语,但我确实知道什么时候需要它。