A new feature i want in my C++: Contexts

Started by
18 comments, last by Telastyn 10 years, 10 months ago
Uhm. The context variables will be just like normal variables. They are simply stored outside the class object.I dont see how there are hidden dependencies, the class tells you it needs certain data to be provided by the user and will produce a compile time error if they are not there. You could remove the context: thing and ot would work exactly as before, except that the object will be bigger. The problem here might be the implicit keyword i added as an extra, that can be made not so evil by requiring context variables to be marked as usable by implicit variables. Ideally they should be a single context class you inherit to lets say add heeap allocation or a logger to pass along. Its better than using globals. And if you are going to pass a class containing all your loggers and managers to every single metgod you call, might as well hide them, especially when calling 10 methods of the same object at once.

o3o

Advertisement
They aren't 'like normal variables' at all as you now have effectively 'magic' happening in the background.

Take your example to clear things up; when you call foo.setAlloc() it looks like you are performing an operation on the class but you aren't, you are doing something to a local variable which isn't even named in the expression.

Right away you have muddied the interface ("Does this function do something to foo itself? or to a context class?") and remove the clarity of what dependencies each function has ("I've created a context, I've set 4 things, I call two functions... which functions depend on what things from the context?") and, based on the reading, you need to provide ALL the details for the classes dependencies up front before you can call any functions (You no longer know what the functions are using, therefore in order to be safe all dependencies, regardless of the function you are calling them needing them are now required to be resolved.)

So, if you create an instance of a class Foo on the stack which requires a Context consisting of 4 things before you can call any function on foo you have to ensure the whole classes context dependencies are completed because you have no way of knowing what dependencies the function requires as they are now hidden away from you.

The argument of 'what if you need to pass all your loggers and managers to every single function' fails the 'what if' test right away - you are attempting to fix a 'problem' which only exists in badly designed code. In every practical situation I've come across the parameters can be either provided at construction of an object or a very limited subset of external systems are required at any given point.

At work we have a gigantic program that relies on an object (let's call its class "Environment", although that's not its actual name). Everything gets passed a pointer to an Environment, and from the Environment you can get access to all sorts of data. There is a global Environment that is used as default in many places if you don't specify one explicitly.

The resulting code is a stinking pile of dung. Everything depends on everything else and it would be impossible to have more than one object of type Environment, because the global one is used in too many places. The program is so large that nobody understands what all parts do, and refactoring this would require rethinking every part of the code, so nobody does it.

In my opinion this type of Environment object is an antipattern, closely related to the big ball of mud. It is much better to make the dependencies explicit by passing pointers or references to all the objects needed, even if it might seem tedious at times.

Editor wont scroll on mobile so ill put this at start instead of end (storing dependencies inside the objects is unacceptable because of the not necessary pointer added, thats just wrong)I was thinking of the context to be used for things which should always be there, jusy as if they were passed and stored during construction. The context should be integral to the functioning of the class, and every method could potentially use them, just like it is with normal members. Any method can use any of the normal members without needing to specify which ones. This would obviously suck for a big class, but for a smaller well defined class it would work. Leaving pieces of the context out would be like not initializing a member of a class and then calling a method - the object is likely in invalid state and will fail. Of course you can just not initialize one of them and see what happens, just like with normal members.

The other alternative is using the above mentioned environment class that gets passed everywhere, and you need many of those dpending on subsystem. Passing individual context thingiea like loggers and all separately wouldnt b nice when you decide to add a new one and have to change every single method in the program - instead of just a single CommonContext class that adds a logger or something to be passed.

o3o


The other alternative is using the above mentioned environment class that gets passed everywhere, and you need many of those dpending on subsystem. Passing individual context thingiea like loggers and all separately wouldnt b nice when you decide to add a new one and have to change every single method in the program - instead of just a single CommonContext class that adds a logger or something to be passed.

That's the kind of thinking that will get you in trouble. If you decide to add a new object in your program, you need to think about what parts of the program need to know about it and pass it where appropriate. If you add it to the CommonContext, you effectively have no design to your program and every part of the program can access every other part. This makes it hard to think about individual parts, prove that they are correct or test them. It also makes it impossible to reuse some part of your program in another program.

Another architecture that might be worth thinking about is signals and slots. This allows each component to not know much about the others. They simply send signals that can be picked up by other components and they have slots where a signal can be plugged in, so when the signal is sent they do something in response. The program initially instantiates all the objects and plugs signals into slots. I have used this in a couple of projects and I like it quite a bit.

I was thinking the CommonContext class for things which are practically globals, allocators and loggers (not much else comes to mind :c)

This might be too complex in its current form to be actually worth it.

I still think it would work for the above mentioned uses, seeing as you can always split the commoncontext class to have different dependencies for different subsystems or simply provide different values for the logger or allocator (one logger for physics another for networking)

In some of the examples i gave in the first post, the physics manager acts as a kind of "RAM". Your game objects only use it for allocation, freeing and accessing. Operations like collision detection between all physical objects are done by outside code. Id like the physics manager act similiar to RAM. I dont need to pass around references to "RAM" when i want to access my objects (because RAM is a global, how evil is that D:)

So, if we made RAM less evil, so that its an object (yay, now we can theoretically have multiple RAMs and write code that is meant to run on multiple machines in a single project!) and needed to access any object, how would you like to handle it?

I would just make all my objects inherit from "RAMContext", and before creating the "Game" or whatever class in main, set the context to a RAM object. Then all my objects could simply assume the variable containing RAM to be there, and access objects like ram.this and ram.that, no need to pass RAM to every single method. (lets ignore the fact that we need to explicitly access all objects via the RAM object)

I want my C++ to not have evil globals like "RAM", it hides a major(eh?) problem with the language! ,_,

o3o

I think what's missing is proper use of objects. The OP tries to masquerade
class Foo{
public: 
SomeType doSomething(BoringManager1 m1, BoringManager2 m2, NiceParameter p);
}
as
class Foo{
public: 
SomeType doSomething(NiceParameter p);
}
but the normal way to "hide" the managers is making them the fields of an object:
class SomethingDoer{
private:
BoringManager1 m1;
BoringManager2 m2;
public: 
SomethingDoer(BoringManager1 m1, BoringManager2 m2);
SomeType doSomething(Foo f, NiceParameter p);
}
In other words, adding a SomethingDoer class to represent the "context" of doSomething() allows the client code to ignore the managers as much as possible while providing the proper means to take care of manager lifetime and associate them together.

Omae Wa Mou Shindeiru

Hah! I was thinking about the exact same thing before and also wanted to name it context. Sometimes static members are used in this manner. Object counters, common configuration data, etc. The problem ist that this static state is global for all instances of the class. Sometimes you only want to share state between a subset of all instances.

But there is no need to add a new concept to C++, you can already do that with what you have at hand. You can either create a context strcuture and pass that to the constrcutor or you can create object collections that hold shared data. I much prefer the latter, as it keeps the class focused on itself and pushes responsibility for interaction into a higher level.

http://en.wikipedia.org/wiki/Dependency_injection. 'nuf said.

"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms

+1 for just pass it in and/or dependency injection.

There's no reason for this to be part of the language. (Though C++'s lack of reflection capabilities limits your options as far as auto-magical solutions go).

This topic is closed to new replies.

Advertisement