如何将值正确设置为相同类型的合成成员

时间:2013-01-24 08:53:26

标签: c++ design-patterns polymorphism

假设我有一个名为Person的班级,他拥有三种宠物:

class Person
{
public:
    accept(Pet a);
private:
    Dog d;  // is a Pet
    Cat c;  // is a Pet
    Fish f; // is a Pet
}

Person::accept(Pet a)
{
    // if pet is Dog, then
    d = a;
    // if pet is Cat, then
    c = a;
    // if pet is Fish, then
    f = a;
};

我想typeid可以在这里使用。但是,它对我来说仍然很奇怪。

是否存在某种可以应用的多态,虚函数或某些OOP模式?

- 编辑 -

对不起这里的坏榜样。让我尝试另一个:

// Usually a context contains three different resources:
class Context
{
public:
    setResource(Resource *r);
private:
    Buffer *b_;    // is a Resource
    Kernel *k_;    // is a Resource
    Sampler *s_;   // is a Resource
};
Context::setResource(Resource *r) { // same logic as Person::accept() above }
Context::handlingBuffer() { if (b_) b_->init(); ... }
Context::run() { 
    if (b_ && k_) { 
        k_.setBuffer(b_); 
        k_.run(); 
    }
}
...

在这种情况下,在Resource *r_[3]中添加Context似乎会让事情变得更复杂。

那么,是否可以将Resource的基类指针传递给setResource(),它可以自动决定要设置哪个资源?

5 个答案:

答案 0 :(得分:5)

由于您按值保留Pets,因此您可以忘记多态并只重载accept成员函数:

class Person
{
public:
    accept(const Dog& a)  { d_ = a; }
    accept(const Cat& a)  { c_ = a; }
    accept(const Fish& a) { f_ = a; }
private:
    Dog d_;  // is a Pet
    Cat c_;  // is a Pet
    Fish f_; // is a Pet
};

答案 1 :(得分:1)

让代码依赖于运行时类型的常用方法是 double dispatch ,a.k.a。访问者模式:

class ResourceContext
{
public:
    virtual void setResource(Buffer* r) = 0;
    virtual void setResource(Kernel* r) = 0;
    virtual void setResource(Sampler* r) = 0;
};

class Resource
{
public:
    virtual void AddToContext(ResourceContext* cxt) = 0;

    [... rest of Resource ...]
};

class Buffer : public Resource
{
public:
     void AddToContext(ResourceContext* cxt) { cxt->SetResource(this); }
};

// Likewise for Kernel and Sampler.

class Context : public ResourceContext
{
public:
    void setResource(Resource* r) { r->AddToContext(this); }
    void setResource(Buffer *r)  { b_ = r; }
    void setResource(Kernel *r)  { k_ = r; }
    void setResource(Sampler *r) { s_ = r; }
private:
    Buffer *b_;    // is a Resource
    Kernel *k_;    // is a Resource
    Sampler *s_;   // is a Resource
};

答案 2 :(得分:0)

对于您的简单示例代码,我同意juanchopanza。但是,如果您想使用指针保留class Person的结构,可以使用dynamic_cast<>,例如

struct Pet { /* ... */ };
struct Dog : public Pet { /* ... */ };
struct Cat : public Pet { /* ... */ };
struct Fish : public Pet { /* ... */ };
struct Spider : public Pet { /* ... */ };

class Person {
  Dog*dog;
  Cat*cat;
  Fish*fish;
  Spider*yuck;

  template<typename PetType>
  static bool accept_pet(Pet*pet, PetType*&my_pet)
  {
    PetType*p = dynamic_cast<PetType*>(pet);
    if(p) {
      my_pet = p;
      return true;
    }
    return false;
  }
public:
  Person()
  : dog(0), cat(0), fish(0), yuck(0) {}
  void accept(Pet*pet)
  {
    if(accept_pet(pet,dog)) return;
    if(accept_pet(pet,cat)) return;
    if(accept_pet(pet,fish)) return;
    if(accept_pet(pet,yuck)) return;
    throw unknown_pet();
  }
};

我应该补充一点,如果可以,应该避免dynamic_cast<>。通常(但不总是)可以改进使dynamic_cast<>成为必要的设计以避免这种情况。这也适用于此,当简单地重载accept()(如在juanchopanza的答案中)是另一种选择。

答案 3 :(得分:0)

对我而言,方法本身看起来是错误的。正如@LihO在他的评论中所说,多态性有助于以相同的方式处理不同类型的对象。所以从多态性的角度来看,你的设计应该看起来像:

// Usually a context contains three different resources:
class Context
{
public:
    setResource(Resource *r);
private:
    std::vector<Resource*> resources_;
};

其余部分应通过Resource类的虚函数来解决。

使用dynamic_cast通常意味着您的设计并不完美。

答案 4 :(得分:0)

受到@molbdnilo的启发,我找到了一种更简单的方法来实现我的目标:

class Resource
{
public:
    virtual void AddToContext(Context* c) = 0;
};

class Buffer : public Resource
{
public:
    void AddToContext(Context* c) { c->SetResource(this); }
};

// Likewise for Kernel and Sampler

class Context
{
public:
    void SetResource(Resource *r) { r->AddToContext(this); }
    void SetResource(Buffer *b) { b_ = b; }
    void SetResource(Kernel *k) { k_ = k; }
    void SetResource(Sampler *s) { s_ = s; }
    ...
private:
    Buffer *b_;
    Kernel *k_;
    Sampler *s_;
};

此时它并不像访客模式,但它相对简洁且效果很好。