How to maintain encapsulation with composition in C++?

I am designing a class Master that is composed from multiple other classes, A, Base, C and D. These four classes have absolutely no use outside of Master and are meant to split up its functionality into manageable and logically divided packages. They also provide extensible functionality as in the case of Base, which can be inherited from by clients.

But, how do I maintain encapsulation of Master with this design?


So far, I’ve got two approaches, which are both far from perfect:

1. Replicate all accessors:

Just write accessor-methods for all accessor-methods of all classes that Master is composed of.
This leads to perfect encapsulation, because no implementation detail of Master is visible, but is extremely tedious and makes the class definition monstrous, which is exactly what the composition should prevent. Also, adding functionality to one of the composees (is that even a word?) would require to re-write all those methods in Master. An additional problem is that inheritors of Base could only alter, but not add functionality.

2. Use non-assignable, non-copyable member-accessors:

Having a class accessor<T> that can not be copied, moved or assigned to, but overrides the operator-> to access an underlying shared_ptr, so that calls like

Master->A()->niceFunction();

are made possible. My problem with this is that it kind of breaks encapsulation as I would now be unable to change my implementation of Master to use a different class for the functionality of niceFunction(). Still, it is the closest I’ve gotten without using the ugly first approach. It also fixes the inheritance issue quite nicely. A small side question would be if such a class already existed in std or boost.


EDIT: Wall of code

I will now post the code of the header files of the classes discussed. It may be a bit hard to understand, but I’ll give my best in explaining all of it.

1. GameTree.h

The foundation of it all. This basically is a doubly-linked tree, holding GameObject-instances, which we’ll later get to. It also has it’s own custom iterator GTIterator, but I left that out for brevity. WResult is an enum with the values SUCCESS and FAILED, but it’s not really important.

class GameTree
{
public:
    //Static methods for the root. Only one root is allowed to exist at a time!
    static void ConstructRoot(seed_type seed, unsigned int depth);
    inline static bool rootExists(){ return static_cast<bool>(rootObject_); }
    inline static weak_ptr<GameTree> root(){ return rootObject_; }

    //delta is in ms, this is used for velocity, collision and such
    void tick(unsigned int delta);


    //Interaction with the tree
    inline weak_ptr<GameTree> parent() const { return parent_; }

    inline unsigned int numChildren() const{ return static_cast<unsigned int>(children_.size()); }
    weak_ptr<GameTree> getChild(unsigned int index) const;
    template<typename GOType>
    weak_ptr<GameTree> addChild(seed_type seed, unsigned int depth = 9001){
        GOType object{ new GOType(seed) };
        return addChildObject(unique_ptr<GameTree>(new GameTree(std::move(object), depth)));
    }                                           
    WResult moveTo(weak_ptr<GameTree> newParent);
    WResult erase();

    //Iterators for for( : ) loop
    GTIterator& begin(){
        return *(beginIter_ = std::move(make_unique<GTIterator>(children_.begin())));
    }
    GTIterator& end(){
        return *(endIter_ = std::move(make_unique<GTIterator>(children_.end())));
    }

    //unloading should be used when objects are far away
    WResult unloadChildren(unsigned int newDepth = 0);
    WResult loadChildren(unsigned int newDepth = 1);

    inline const RenderObject& renderObject() const{ return gameObject_->renderObject(); }

    //Getter for the underlying GameObject (I have not tested the template version)
    weak_ptr<GameObject> gameObject(){
        return gameObject_;
    }
    template<typename GOType>
    weak_ptr<GOType> gameObject(){
        return dynamic_cast<weak_ptr<GOType>>(gameObject_);
    }

    weak_ptr<PhysicsObject> physicsObject() {
        return gameObject_->physicsObject();
    }

private:
    GameTree(const GameTree&); //copying is only allowed internally
    GameTree(shared_ptr<GameObject> object, unsigned int depth = 9001);

    //pointer to root
    static shared_ptr<GameTree> rootObject_;

    //internal management of a child
    weak_ptr<GameTree> addChildObject(shared_ptr<GameTree>);
    WResult removeChild(unsigned int index);

    //private members
    shared_ptr<GameObject> gameObject_;
    shared_ptr<GTIterator> beginIter_;
    shared_ptr<GTIterator> endIter_;

    //tree stuff
    vector<shared_ptr<GameTree>> children_;
    weak_ptr<GameTree> parent_;
    unsigned int selfIndex_; //used for deletion, this isn't necessary
    void initChildren(unsigned int depth); //constructs children
};

2. GameObject.h

This is a bit hard to grasp, but GameObject basically works like this:

When constructing a GameObject, you construct its basic attributes and a CResult-instance, which contains a vector<unique_ptr<Construction>>. The Construction-struct contains all information that is needed to construct a GameObject, which is a seed and a function-object that is applied at construction by a factory. This enables dynamic loading and unloading of GameObjects as done by GameTree. It also means that you have to define that factory if you inherit GameObject. This inheritance is also the reason why GameTree has a template-function gameObject<GOType>.

GameObject can contain a RenderObject and a PhysicsObject, which we’ll later get to.

Anyway, here’s the code.

class GameObject;
typedef unsigned long seed_type;


//this declaration magic means that all GameObjectFactorys inherit from GameObjectFactory<GameObject>
template<typename GOType>
struct GameObjectFactory;

template<>
struct GameObjectFactory<GameObject>{
    virtual unique_ptr<GameObject> construct(seed_type seed) const = 0;
};

template<typename GOType>
struct GameObjectFactory : GameObjectFactory<GameObject>{
    GameObjectFactory() : GameObjectFactory<GameObject>(){}
    unique_ptr<GameObject> construct(seed_type seed) const{
        return unique_ptr<GOType>(new GOType(seed));
    }
};

//same as with the factories. this is important for storing them in vectors
template<typename GOType>
struct Construction;

template<>
struct Construction<GameObject>{
    virtual unique_ptr<GameObject> construct() const = 0;
};

template<typename GOType>
struct Construction : Construction<GameObject>{
    Construction(seed_type seed, function<void(GOType*)> func = [](GOType* null){}) :
        Construction<GameObject>(),
        seed_(seed),
        func_(func)
    {}

    unique_ptr<GameObject> construct() const{
        unique_ptr<GameObject> gameObject{ GOType::factory.construct(seed_) };
        func_(dynamic_cast<GOType*>(gameObject.get()));
        return std::move(gameObject);
    }

    seed_type seed_;
    function<void(GOType*)> func_;
};


typedef struct CResult
{
    CResult() :
        constructions{}
    {}

    CResult(CResult && o) :
        constructions(std::move(o.constructions))
    {}

    CResult& operator= (CResult& other){
        if (this != &other){
            for (unique_ptr<Construction<GameObject>>& child : other.constructions){
                constructions.push_back(std::move(child));
            }
        }
        return *this;
}

    template<typename GOType>
    void push_back(seed_type seed, function<void(GOType*)> func = [](GOType* null){}){
        constructions.push_back(make_unique<Construction<GOType>>(seed, func));
    }

    vector<unique_ptr<Construction<GameObject>>> constructions;
} CResult;



//finally, the GameObject
class GameObject
{
public:
    GameObject(seed_type seed);
    GameObject(const GameObject&);

    virtual void tick(unsigned int delta);

    inline Matrix4f trafoMatrix(){ return physicsObject_->transformationMatrix(); }


    //getter
    inline seed_type seed() const{ return seed_; }
    inline CResult& properties(){ return properties_; }
    inline const RenderObject& renderObject() const{ return *renderObject_; }
    inline weak_ptr<PhysicsObject> physicsObject() { return physicsObject_; }

protected:
    virtual CResult construct_(seed_type seed) = 0;

    CResult properties_;
    shared_ptr<RenderObject> renderObject_;
    shared_ptr<PhysicsObject> physicsObject_;
    seed_type seed_;
};

3. PhysicsObject

That’s a bit easier. It is responsible for position, velocity and acceleration. It will also handle collisions in the future. It contains three Transformation objects, two of which are optional. I’m not going to include the accessors on the PhysicsObject class because I tried my first approach on it and it’s just pure madness (way over 30 functions). Also missing: the named constructors that construct PhysicsObjects with different behaviour.

class Transformation{
    Vector3f translation_;
    Vector3f rotation_;
    Vector3f scaling_;
public:
    Transformation() :
        translation_{ 0, 0, 0 },
        rotation_{ 0, 0, 0 },
        scaling_{ 1, 1, 1 }
    {};
    Transformation(Vector3f translation, Vector3f rotation, Vector3f scaling);

    inline Vector3f translation(){ return translation_; }
    inline void translation(float x, float y, float z){ translation(Vector3f(x, y, z)); }
    inline void translation(Vector3f newTranslation){
        translation_ = newTranslation;
    }
    inline void translate(float x, float y, float z){ translate(Vector3f(x, y, z)); }
    inline void translate(Vector3f summand){
        translation_ += summand;
    }

    inline Vector3f rotation(){ return rotation_; }
    inline void rotation(float pitch, float yaw, float roll){ rotation(Vector3f(pitch, yaw, roll)); }
    inline void rotation(Vector3f newRotation){
        rotation_ = newRotation;
    }
    inline void rotate(float pitch, float yaw, float roll){ rotate(Vector3f(pitch, yaw, roll)); }
    inline void rotate(Vector3f summand){
        rotation_ += summand;
    }

    inline Vector3f scaling(){ return scaling_; }
    inline void scaling(float x, float y, float z){ scaling(Vector3f(x, y, z)); }
    inline void scaling(Vector3f newScaling){
        scaling_ = newScaling;
    }
    inline void scale(float x, float y, float z){ scale(Vector3f(x, y, z)); }
    void scale(Vector3f factor){
        scaling_(0) *= factor(0);
        scaling_(1) *= factor(1);
        scaling_(2) *= factor(2);
    }

    Matrix4f matrix(){
        return WMatrix::Translation(translation_) * WMatrix::Rotation(rotation_) * WMatrix::Scale(scaling_);
    }
};

class PhysicsObject;

typedef void tickFunction(PhysicsObject& self, unsigned int delta);

class PhysicsObject{
    PhysicsObject(const Transformation& trafo) :
        transformation_(trafo),
        transformationVelocity_(nullptr),
        transformationAcceleration_(nullptr),
        tick_(nullptr)
    {}
    PhysicsObject(PhysicsObject&& other) :
        transformation_(other.transformation_),
        transformationVelocity_(std::move(other.transformationVelocity_)),
        transformationAcceleration_(std::move(other.transformationAcceleration_)),
        tick_(other.tick_)
    {}

    Transformation transformation_;
    unique_ptr<Transformation> transformationVelocity_;
    unique_ptr<Transformation> transformationAcceleration_;

    tickFunction* tick_;

public:
    void tick(unsigned int delta){ tick_ ? tick_(*this, delta) : 0; }

    inline Matrix4f transformationMatrix(){ return transformation_.matrix(); }
}

4. RenderObject

RenderObject is a base class for different types of things that could be rendered, i.e. Meshes, Light Sources or Sprites. DISCLAIMER: I did not write this code, I’m working on this project with someone else.

class RenderObject
{
public:
    RenderObject(float renderDistance);
    virtual ~RenderObject();

    float renderDistance() const { return renderDistance_; }
    void setRenderDistance(float rD) { renderDistance_ = rD; }


protected:
    float renderDistance_;
};

struct NullRenderObject : public RenderObject{
    NullRenderObject() : RenderObject(0.f){};
};

class Light : public RenderObject{
public:
    Light() : RenderObject(30.f){};
};

class Mesh : public RenderObject{
public:
    Mesh(unsigned int seed) :
        RenderObject(20.f)
    {
        meshID_ = 0;
        textureID_ = 0;
        if (seed == 1)
            meshID_ = Model::getMeshID("EM-208_heavy");
        else
            meshID_ = Model::getMeshID("cube");
    };

    unsigned int getMeshID() const { return meshID_; }
    unsigned int getTextureID() const { return textureID_; }

private:
    unsigned int meshID_;
    unsigned int textureID_;
};

I guess this shows my issue quite nicely: You see a few accessors in GameObject which return weak_ptrs to access members of members, but that is not really what I want.

Also please keep in mind that this is NOT, by any means, finished or production code! It is merely a prototype and there may be inconsistencies, unnecessary public parts of classes and such.

18

The whole game industry switched to Component-based Design engines (for re-usable engines only) for the reason of composability with performance constraints (and allow also fully data-driven engines). You should look into it, it’s the best way we know to have very heterogeneous kind of entities, defined by a set of different components.

There is a lot of different ways to setup a component system. I should point to the book Game Engine Architecture which have a whole chapter on the different kind of setup you can have to solve the problem you have, including different kind of component-based systems. Also there are a lot of articles about the subject like:

  • http://gameprogrammingpatterns.com/component.html

(there are also a lot of experimentation on github about how to implement a component system in modern C++. I did one recently with no inheritance at all, using concept-based type-erasure)

The issues you have are related to the use of inheritance where it makes the architecture less flexible than it could. Remove inheritance and organize things in batches of components and you will have maximum flexibility.

Note that, of course, that flexibility is not always what you want in the end, but it should solve the problem you are presenting here.

4

I wanted to try to complement Klaim’s fine answer with some alternative details.

But, how do I maintain encapsulation of Master with this design?

You don’t if I understood the context correctly. That’s a slightly dogmatic-sounding statement but unless someone figures out a brand new way of designing and implementing objects here in the face of the flexible and programmable and open architecture requirements of game engines, it is not practical to expect to model a concept like a World, or Universe, or most commonly used term, Scene, and expect its public interface to service every possible thing you can ever want to do with the “universe” without exposing every little thing that resides inside of it.

The common approach here is to favor what might be called a “leaky abstraction” and think of your Scene, or Universe, as a container of sorts, with the primary responsibility of storing things and allowing the users of its interface to access whatever exists inside of it. Trying to make it hide away such details in favor of the strongest level of encapsulation and information hiding while maintaining invariants tends to grow into an impossibly monolithic responsibility that grows and grows and grows with ever-changing requirements.

Just write accessor-methods for all accessor-methods of all classes that Master is composed of. This leads to perfect encapsulation, because no implementation detail of Master is visible […]

I don’t consider that “perfect encapsulation”. That might be a bit subjective but what I consider the strongest level of encapsulation is a class whose state doesn’t even need to be exposed in the first place (through accessor functions or anything else). In my mind it’s a class that exposes the absolutely minimal information about itself, like a container that doesn’t even need to expose what’s stored inside for anything other than read-only access. That is the strongest way to maintain invariants over its state is to not expose it in any mutable form whatsoever. If we want to maintain Universe-level invariants in our Universe class/interface, then we cannot expose anything inside of it for tampering. Of course, as mentioned above, that tends to not be very practical when we’re modeling things at the level of a “Universe” with the types of shifting requirements imposed on game engines which often want to model a “Universe” of sorts (however simplified, gamedevs are creating their own miniature universe, and I’d dare say that is one of the most challenging things to do from a programming design standpoint as they are literally trying to be “gods” breathing life into some universe whether it’s Super Mario or Unreal 4).

Disparate Types of Things

And this is where I think you found a problem (with the Master, or GameTree, as I gathered you call it). Even if we make our Universe, or Scene, into a container, it doesn’t want to contain very homogeneous types of things.

We might have stars which emit light, we might have plants which depend on such light to grow, and wither and die in its absence, we might have people, animals which feed on each other and plants, vehicles, machine guns, artificial lights, cameras, buildings, etc etc etc.

So there’s not a very homogeneous concept or design or interface here to unify all these things. So games usually solve this problem by making the individual things inside of our Scene into something akin to “containers” themselves, called an Entity (which is like your GameObject).

And we might have a sentry entity which has a camera component so that you can look through it from a security station, it might have a directional light component so that it shines infrared light in the direction it’s looking at, it might have a weapon component to fire when intruders get in its line of sight, and just for fun it might also be an organic plant with a plant component (it’s like a cybernetic plant thing) that requires sunlight to survive and its weapon component is designed to shoot spiked needles at intruders.

Houston: We Still Have a Problem

Uh oh, we just transferred the non-homogeneous collection problem to these game Entities. How are we supposed to fetch things as disparate as plant components from camera components from an Entity? And there often the interface of said Entity will tend to involve some down casts in its implementation (but checked centrally at runtime for safety), like:

// Fetch a plant component from the entity, or nullptr if
// if the entity does not provide one.
PlanetComponent* plant = some_entity.get<PlantComponent>();
If (plant)
{
     // Do something with the plant.
}

And how you implement that can vary wildly, but often you’ll find some central downcast somewhere in the implementation of the analogical get method above (might be a dynamic_cast; I have some answer somewhere on here where I suggested one possible implementation). The implementation usually combines a polymorphic base pointer container of sorts with a downcast (which is frowned upon, but at least we can centralize it to one place in the system).

Systems

If we get this far, then we might want to organize our codebase into “Systems”. You might have a NatureSystem whose sole responsibility is to loop through all entities in our universe which provide plant components, and make sure they have available sunlight to grow. If they don’t, they might begin to wither and die.

Given how frequently such systems might want to do such things, we might add a public method to our Scene to fetch all plant components in our universe:

for (PlantComponent& plant: scene.query<PlantComponent>())
{
     // Make sure plant component has had sufficient available 
     // sunlight or else make it start withering and dying.
}

And this way we don’t even have to bother looping through our entities and checking if they provide a plant component. We just ask the scene what plant components are available to process in some loop.

Efficiency

If we start profiling code like the above, we might find a lot of hotspots in scene.query<SomeComponentType>() which might have to loop through all entities in our “universe” and check to see if they provide some component of a given type, like a plant component.

So that might raise the question if components should really be implemented like containers. For that I tried to consult with the high elders of game programming and, if I remember the holy scripture correctly, their answer went something like this:

No.

Instead it might be a lot more efficient for these systems performing loops to store each component of a particular type contiguously in our Scene (or universe), and have entities become associated/mapped to the components they conceptually “have”.

enter image description here

And with that, when a system wants all plant components in our “universe”, or Scene, you no longer have to go through the entities at all. You can just directly access the collection of plant components in a scene and iterate through it (with a nice, contiguous representation that preserves locality of reference). We don’t store components inside of entities. We associate entities to their components.

Pure Interfaces

Just a brief mention, but you might wonder why we don’t use pure interfaces, like IPlant. In that case we could see if an entity provides “plant capabilities” like so:

IPlant* plant = entity.get<IPlant>();

Or fetch everything that provides plant interfaces in a scene like so:

for (IPlant& plant: scene.query<IPlant>())
{
     ...
}

While this gives lots of flexibility to the user of such interfaces, the problem with that approach is that your entities will generally want to implement these pure interfaces directly or directly store components that do. The nature of such an architectural design might also tempt developers to redundantly implement such an interface with little benefit in terms of Liskov substitution (just mostly code duplication), or introduce some abstract base class to eliminate the redundancy which largely defeats the whole point and substitutability of pure interfaces.

Here such flexibility comes about from the separation of components and the logic (systems) which processes them rather than the substitutability of the implementation of a given component. We end up getting the sort of flexibility of duck typing as found in C++ templates, where if something has wings, it can flap them and begin flying. We don’t care about what it is beyond that except for the fact that it has wings, and the wings might even just be data instead of some abstract interface.

Conclusion

So with all that, we arrive at the entity-component system architecture, as popular among game engines today (and I’m the only oddball to my knowledge using it in VFX software now). And I don’t know if the “evolution story” above is true or not. I just made it up. But I like telling it and it makes sense to me as a former gamedev starting in Borland Turbo C tackling the exact same design problems all along of how to model a “universe”, but finding the ECS the nicest fit I’ve found so far to these same design problems I’ve faced all these years. I like telling stories like these. I’m not sure if my sense of humor translates in text so well. I’m much better at it in person. Drunk girls seem to dig it (well, I mean not programming stories in those cases; usually you don’t want to tell girls in night clubs that you’re a programmer until they’ve already started to like you).

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật