Question on OOP design preferences.

Started by
5 comments, last by Qw3r7yU10p! 19 years, 9 months ago
Hey all, I'm strictly talking C++ here when I talk about OOP. What i'm wondering is how some of you handle certain functions which are repeated throughout a program. Obviously you try to design away form this, but it's bound to happen. Something like an output function maybe which could be needed all over for bug reporting to talking. Anyway, how do some of you handle functions like this generaly? I really only see three options myself... 1)Independant global functions in a header somewhere. yuck. That's all i've really got to say about that. 2)You can wrap the function, or the group if they are related functions in a singleton and then grab the instance wherever you need it. This one seems fairly popular. But there's something about a singleton that just seems like cheating. 3)Wrap your functions in a class definition but make them static, so you can do CClass::Function(); But isn't this almost if not exactly the same as a global function in some header? You'd never Instantiate the class, so they are just functions in memory you're calling, does the scope help any? Also seems like cheating. I have no problem using a singleton to stop extra Instantiating, but using it just to cart around a function bugs me. Static functions hiding in a class definition bug me more. And global functions? ew =) So... I'm wondering what are some methods other people have used?
- Newb Programmer: Geek++
Advertisement
Non-member functions are perfectly fine, and even sometimes required (e.g. mixed-mode arithmetic operators).

C++ namespaces are much better suited to hold functions than a class that would only have static members. And no, it's not cheating.
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it." — Brian W. Kernighan
Quote:Original post by GeekPlusPlus
I really only see three options myself...
1)Independant global functions in a header somewhere. yuck. That's all i've really got to say about that.


I agree with Fruny. Why yuck? Call a duck a duck, what you want is exactly that: an independent globally accessible function.

OOP is not a silver bullet.... just because you have a good hammer, don't make all the problems nails.
It's only funny 'till someone gets hurt.And then it's just hilarious.Unless it's you.
What's your problem with (1)?

Here's what I like to do:

// File log.h#ifndef LOG_H#define LOG_H#define g_logStream (*g_pLogStream)extern std::ostream* g_pLogStream;#endif// File log.cpp#include <iostream>#include "Log.h"// The output logging stream. This is initalized to std::cerr, // but can be set to another stream object anytime.std::ostream* g_pLogStream = &std::cerr;



With this, you just #include "log.h" wherever you want to use the logging stream. Usage:

#include "log.h"// ...void foo() {   g_logStream << "Hello, world!" << std::endl;}


If you want to log to a file instead of std::cerr, you can set the g_pLogStream pointer to point to a file stream that you open at the start of the program and close before the program exits. It's easy to write, easy to use, and doesn't clutter things up much. Wrap all this in a namespace if you're worried about collisions and stuff.
If you have a system where every object is "owned" by another object then you would have a base object at the root of a tree. This object (e.g. the application, or engine) could provide services such as logging. Any other object wanting to log something would pass on the request to its owner. Sort of like the ChainOfResponsibility Pattern.

If every object points to its owner already then it's not adding any burden but if you decide to introduce it to a design then there is a memory overhead of everything knowing what owns it. The calling code will be longer than just accessing one global object as the request would get passed along until it reached the root of the tree.

You would also be able to customise the response so objects owned by a particular object might have their requests for logging directed to a different log rather than the parent one.

It's an alternative. Hope it helps.

Pete
I've often run into one problem in the ownership tree: the inability to bring in the namespace of parent objects. Am I alone in wishing you could "import" an instance's namespace in C++, similar to the "with" operation in visual basic (or "using" with namespaces). I often find it would be very handy with parent objects. Yes, it might also be somewhat obfuscating, but having to use -> operators all over the place to access the core engine functions is a big pain.
-- Single player is masturbation.
Quote:Original post by Pxtl
I've often run into one problem in the ownership tree: the inability to bring in the namespace of parent objects. Am I alone in wishing you could "import" an instance's namespace in C++, similar to the "with" operation in visual basic (or "using" with namespaces). I often find it would be very handy with parent objects. Yes, it might also be somewhat obfuscating, but having to use -> operators all over the place to access the core engine functions is a big pain.


I think what you are talking about is having an object navigating all the way back up to the core, from the leaf object itself. What I meant was you may have a number of hierarchies and individual classes each of which will have a log(string) function and each of which can if it wishes forward that to its owner.

#include <string>#include <iostream>class Owner {    Owner* m_owner;public:    Owner(Owner* owner) : m_owner(owner) {}    //default Log just calls owner's Log function    virtual void Log(const std::string& message) {        if(m_owner)            m_owner->Log(message);    }};//don't have to derive from Owner as not overriding virtual Logclass BaseGameEntity {    Owner* m_owner;public:    BaseGameEntity(Owner* owner) : m_owner(owner) {}    void Log(const std::string& message) {        m_owner->Log(message);    }};class Player : public BaseGameEntity {public:    Player(Owner* owner) : BaseGameEntity(owner) {}    void SomeFunc() {        Log("called SomeFunc");//calls BaseGameEntity::Log->Owner::Log    }};class Application : public Owner {public:	Application() : Owner(0) {}    //override Owner function so that it actually logs to std::cout    virtual void Log(const std::string& message) {        std::cout << message << "\n";    }    void Main() {        Player player(this);//'this' Application owns the player        player.SomeFunc();    }};int main() {    Application app;    app.Main();    return 0;}


[Edited by - petewood on July 12, 2004 7:11:46 AM]

This topic is closed to new replies.

Advertisement