【问题标题】:c++ ensure object is assigned to a set of objectsc ++确保将对象分配给一组对象
【发布时间】:2014-07-27 16:08:50
【问题描述】:

我正在编写的建模和仿真程序中存在设计问题。模型的所有组件都组织成一个树形数据结构,并且要使其工作,每个组件都需要有一个指向其父对象的指针。为了避免技术细节,我做了一个类比:

我有一个处理所有树功能的基类:

class node {};

以及以下代表模拟组件的派生类:

class turtle : public node {};
class clam : public node {};
class dog : public node {};

还有更多的子组件:

class leg : public node {
  node* _parent;
  /*Some functions relevant to a leg*/
};

class shell : public node {
  node* _parent;
  /*Some functions relevant to a shell*/
};

我的问题是我想确保一条腿只分配给乌龟或狗,而贝壳只分配给乌龟或蛤蜊。 dog 类没有处理shell 类型的子类的功能,同样,clam 也不能处理leg 类型的子类。

我正在考虑制作shellleg 类模板,它们的父类型将是模板参数。然后我可以只为我希望每个类拥有的父类型制作构造函数模板特化。这是解决这个问题的好方法,还是我应该看看其他一些 c++ 特性?

【问题讨论】:

  • 您陷入了典型的“继承必须符合真实世界模型”的陷阱。请参阅:圆形与形状问题。

标签: c++


【解决方案1】:

您可以为确实可用的功能和属性提供接口,并根据需要将它们混合在一起。在 C++ 中创建接口使用纯虚函数

class leg;
class shell;

struct IAnimalWithShell {
    void addShell(shell* shell) = 0;
    ~IAnimalWithShell() = default;
};

struct IAnimalWithLegs {
    void addLegs(const std::vector<leg*>&) = 0;
    ~IAnimalWithLegs() = default;
};

class turtle : public node, public IAnimalWithShell, public IAnimalWithLegs {
    // ...
public:
    void addShell(shell* shell) {
        // Implementation
    }
    void addLegs(const std::vector<leg*>& legs) {
        // Implementation
    }
};

class clam : public node, public IAnimalWithShell {
    // ...
public:
    void addShell(shell* shell) {
        // Implementation
    }
};

class dog : public node, public IAnimalWithLegs {
    // ...
public:
    void addLegs(const std::vector<leg*>& legs) {
        // Implementation
    }
};

希望你能明白。

正如您所要求的设计,这里有一些相关的模式

这些讨论了上述结构设计的后果。

【讨论】:

  • 谢谢,这是最适合我情况的答案。我自己尝试使用多重继承来做到这一点,但无法弄清楚。你的方法很有意义。
  • @user3879653 不太确定,这意味着对可用接口进行大量运行时检查。您使用模板的主要方法并不是一个坏主意。我添加了另一个详细说明此问题的答案。
  • 是什么阻止了有人用一条腿作为参数调用 addshell ?我认为你提到了 addshell(shell* myshell) 而不是 node*
【解决方案2】:

我会亲自将约束检查代码移到专用的工厂类中,例如:

class TurtleFactory {
public:
    node * newTurtle() const {
        node * t = new turtle;
        for (int i = 0; i < 4; ++i)
            t->addChild(new leg);
        t->addChild(new shell);
        return t;
    }

    friend class turtle;
    friend class shell;
    friend class leg;
};

并将turtlelegshell 类的构造函数设为私有,因此仅允许TurtleFactory 实例实际创建海龟。
此外,leg 类将由 dog 为朋友,shell 类将由clam 为朋友。

这种基于工厂的方法通过查看代码可以清楚地表明乌龟有四条腿和一个壳。

但是,只有在“一次性”构建海龟时才会执行此操作。如果我怀疑,您的软件允许以交互方式逐块构建“动物”,那么这是行不通的。

在这种情况下,组件可以携带动态类型信息,如下所示:

enum Type { TURTLE, CLAM, DOG, LEG, SHELL /* ... etc */ };

class node {
    virtual Type getType() = 0;
    virtual bool canAdd(Type t) = 0;

    void addChild(node * child) {
        if (canAdd(child->getType())
            // add node as child
    }
};

class leg {
    virtual Type getType() { return LEG; }
    ...
}

class turtle {
    ...
    virtual bool canAdd(Type t) { return t == LEG || t == SHELL; }
}

每只海龟还可以计算添加的部分数量,以禁止添加超过允许的部分:

    virtual bool canAdd(Type t) { 
        if (t == LEG && nLegs < 4) return true;
        else if (t == SHELL && nShell < 1) return true;
        else return false;
    }

【讨论】:

  • 感谢您的回复。不幸的是,在我的实际问题中,海龟、蛤蜊和狗可以有任意数量的壳和腿,用户可以在运行时交互式地添加/删除它们。您的方法是我最初想到的方法,但我认为应该有一种“更干净”的方法,不涉及任何类型的运行时类型检查。
【解决方案3】:

可能您的示例与源任务不同,但我应该提一下:

你不能从leg继承turtle,所以leg类只能通过组合插入turtle类。这意味着turtle 用户不能直接访问leg 类方法,您应该在turtle 和/或其父级中实现它们。或者,如另一个答案所述,使用接口。

我认为编排这个类的最好方法是:

1) 将leg 类中的所有方法设为私有以禁用直接继承和/或组合。

2) 为leg 创建一个新类LegMoving,它是friend 类,并且知道如何控制legleg 将成为 LegMoving 类的成员。

3) 从LegMoving 继承turtle

我认为这样你不会违反基本 OOP 模式。

【讨论】:

    【解决方案4】:

    糟糕,我正要完全重新设计我的答案,但你接受了。

    但是好的,请考虑以下作为附加信息。

    “我正在考虑制作 shell 和 leg 类模板,它们的父类型将是模板参数。”

    一般来说,这种设计模式被称为 Curiously recurring template Pattern,可以很好地替代动态多态性(运行时类型检查)。
    如果您的用例只需要在编译时构建这些实例,您可以直接使用这种方式。

    考虑一个legs 类模板,它为具体类提供已知数量的腿

    class leg : public node {
    public:
        void moveForward(int speed) {
            // Any behavioral implementation
        }
    };
    
    template<class Impl, size_t LegsCount> 
    class legs {
    public:        
        // Provide a public interface to interact with legs
        void moveForward(size_t legIndex) {
            // Delegate behavioral implementation to Impl
            self->doMoveForward(legIndex);
        }
        size_t getLegsCount() const { return LegsCount; } 
    
        // eventually provide a default behavior that can be called from 
        // classes implementing the interface
        void doMoveForward(size_t legIndex) {
            if(legIndex >= LegsCount) {
                throw std::out_of_range();
            }
        }
    protected:
        legs(const std::array<const std::array<leg,LegsCount>& legs) 
        : self(static_cast<Impl>(self)) 
        , legs_(legs) {
        }
    private:
        Impl* self;
        std::array<leg,LegsCount> legs_;
    };
    

    进一步考虑具有某些特征的 shell 类

    struct shell_base {
        const bool CanOpenShell;
    
        virtual void openShell() = 0;   
    
        shell_traits(bool canOpen = false) : CanOpenShell(canOpen) {}  
    };
    
    template<class Impl> 
    class shell : public shell_base {
    public:
        // Provide a public interface to interact with legs
        void openShell() {
            if(CanOpenShell) {
               // Check if the operation is applicable
               if(!CanOpenShell) {
                   throw std::domain_error("Can't open shell.");
               }
               // Delegate behavioral implementation to Impl
               self->doOpenShell();
            }
        }
    
        void doOpenShell() {
        }
    
    protected:
        shell(bool canOpen) : shell_traits(canOpen) {
        }
    };
    

    你的类声明将如下所示

    class turtle 
    : public node
    , public legs<turtle,4>
    , public shell<turtle> {
    public:
         turtle() 
         : legs<turtle,4>({leg(),leg(),leg(),leg()})
         , shell<turtle>(false) {
         }
         void doMoveForward(size_t legIndex) {
             // Let the base implementation check the range first
             legs<turtle,4>::doMoveForward(legIndex);
    
             // Call the specific behavior
             legs_[legIndex]->moveForward(10); // at slow speed
         }
    };
    

    class clam 
    : public node
    , public shell<clam> {
    public:
         clam() : shell<clam>(true) {
         }
        void doOpenShell() {
             // Implementation
        }
    };
    

    class dog 
    : public node
    , public legs<dog,4> {
    public:
         dog() : legs<dog,4>({leg(),leg(),leg(),leg()}) {
         }          
         void doMoveForward(size_t legIndex) {
             // Let the base implementation check the range first
             legs<dog,4>::doMoveForward(legIndex);
    
             // Call the specific behavior
             legs_[legIndex]->moveForward(100); // at fast speed
         }
    };
    

    【讨论】:

    • 作为后续,CRTP 文章对我很有用,事实上我已经在我的一些实验代码中使用了这个概念来解决这个问题。我当前的解决方案使用了该线程中提出的各种概念;即多重继承和使用 CRTP 来避免一些运行时类型检查。不幸的是,我目前的解决方案仍然涉及运行时多态性,即使我知道它可以被消除。但是,我认为完全消除运行时类型检查会降低代码的可读性,并且无论如何可能不会增加实际性能。
    猜你喜欢
    • 2017-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-15
    • 1970-01-01
    • 1970-01-01
    • 2017-01-25
    相关资源
    最近更新 更多