S
S
safinaskar2017-06-20 19:39:40
OOP
safinaskar, 2017-06-20 19:39:40

Why is it necessary to build the entire program in OOP in C++ (long question)?

I am a C++ programmer. I want to understand why it is considered good practice to build an entire program based on OOP. I will give my view on OOP, and I want you to explain to me where I am wrong.
A few words about terminology. By "object" I mean "an object in the OOP sense" and not what is meant by objects in the C++ standard and on cppreference.com ( en.cppreference.com/w/cpp/language/object ).
I understand perfectly why you need to program without goto. Why do you need to break code into functions. But I do not see the need for the widespread use of OOP. All the justifications for OOP seem murky, OOP itself - hype.
Yes, you can say that OOP is needed to make the code look prettier and cleaner, and to make it easier for another programmer to edit it after me. I will answer this way: "I can explain myself, I want to understand" (c). What is the difference between splitting into classes and the usual splitting into functions, I do not understand. And why in the first case it will be supposedly easier for another programmer working after me.
OOP is three whales: encapsulation, inheritance and polymorphism (while polymorphism is understood as dynamic polymorphism). Of these three, it seems to me that there is often a need for only one: encapsulation. And in one more, not included in this trinity: RAII. As for inheritance and polymorphism, they are rarely needed.
But even encapsulation and RAII, in my opinion, are needed only where they are needed. Let's say we own some kind of resource - a file descriptor or something else - we need RAII. Or there is some data to which we want to provide some interface (perhaps guaranteeing the preservation of some invariants of this data) and prohibit changing them directly - encapsulation is needed (that is, in C ++ this will be expressed by a class with private and public member functions). And why it is necessary to build the entire program based on OOP - I do not understand.
As for inheritance and polymorphism, I can only give a few examples where they are needed. (That said, I think that inheritance is only needed in conjunction with polymorphism.) The first is widget libraries such as Qt. I will explain why inheritance and polymorphism are needed there, in my opinion. There is a large widget, say a window, on which other small widgets are located, say buttons, checkboxes, etc. The large widget stores a list of small widgets. Let's say a large widget needs to draw itself. To do this, you need to draw all the small widgets. And to do this, it iterates over them all and calls a member function on each, say, draw. The problem is that each of the small widgets is drawn differently, that is, the draw function must be different for each. But at the same time, a large widget must somehow be iterated over all in one cycle and call this draw function, which each has its own, in some way. How to do it? You need to make a base class and call it, say, QWidget (as it is called in Qt), declare a virtual draw function for it, inherit other widgets from QWidget and redefine this draw for them. A large widget will store a list of QWidgets by pointer. It will iterate over them and call draw with a virtual call. Ready. A large widget will store a list of QWidgets by pointer. It will iterate over them and call draw with a virtual call. Ready. A large widget will store a list of QWidgets by pointer. It will iterate over them and call draw with a virtual call. Ready.
That's just a similar situation in conventional programs (outside of widget libraries) occurs extremely rarely. In this case, inheritance and polymorphism turned out to be necessary, because we have a pointer to the "widget in general" in this place, we do not know at this place in the code what kind of widget it is, but we need to call draw, which can be different depending on the type widget. But this rarely happens in programs outside of widget libraries. Where in general can it turn out that we have a pointer to an entity, and we don’t know what it is exactly?
Okay, well, this can happen, but only sometimes.
Another example I can think of is AST (abstract syntax tree) storage. There, for example, nodes that store expressions are represented by the Expression type, from which various specific expressions are inherited, say, FunctionCall, Addition, Multiplication, and so on. And Expression has virtual functions for various manipulations with this node. But again, this is just a very special case. And even in it you can find fault. Let's say we make the expression just a std::variant that holds different variants of that expression. Then no inheritance and polymorphism is needed. When you need to do some manipulations with an expression, we do a switch by the type of the expression. You might argue that if you add a new type of expression, then you have to change everything. Well, yes. In the case of OOP, a lot of things would also have to be changed. You say, that when adding a new type of expression, everything will have to be recompiled. Well, yes. In typical programming languages ​​such as C++, new types of expressions are added extremely rarely, well, you have to recompile the compiler once again every year, nothing will happen. You will say that virtual call is faster than switch. Well, maybe it is, but then it turns out that in this particular case, the advantage of OOP is only in speed. There are no philosophical justifications here.
Let's take STL. STL' classes do not have inheritance and polymorphism. More precisely, vector often inherits from vector_base, but this is just an implementation detail not visible to the vector's user. Yes, and this inheritance itself cannot be called idiomatic, because idiomatic inheritance, as I understand it, implies the relation "is", i.e. it turns out that "vector is vector_base", what kind of nonsense? It is not recommended to inherit from vector. But STL classes have encapsulation and RAII, just what I understand well. I really understand why vector has encapsulation and RAII. But without inheritance and polymorphism, all this STL works fine, which, in my opinion, proves their uselessness.
At one interview, I was asked to tell how I would start writing Tetris. How would I break this application into classes. This is meant to be a graphical application. The figurines are colorful and beautiful. There is a score counter, everything is as it should be. And I couldn't answer. I don't see the need for classes here. That rare situation, as was the case with those widgets, when we have a pointer to it is not clear what, is not here. So inheritance and polymorphism are not needed here. Some data, which should have a certain invariant, to which you want to provide a certain interface, is also missing here. Hence, encapsulation is also not needed. That's it, objects are not needed here. Functions needed. We need structs. They tell me that the application has a main menu, in which you can select the "play" item and go to the main playing field. Well, okay, so what? It just means that when you click on "play" a function should be called that will be responsible for the gameplay itself. And when the game is over, this function should return and return control to the function responsible for the menu. Where are the objects? Again not needed.
I like the Linux kernel code. There is no division of the program into objects, and everything is okay there, they live perfectly without such a division. True, polymorphism is still there in some form. For example, there is a struct file (which is responsible for the open file), which stores a pointer to struct file_operations (I looked at the sources of Linux 4.11.6). struct file_operations is a kind of table of virtual functions. And it stores pointers to operations that can be done with files. A kind of virtual function. And they are really needed. Because there are places in the kernel where we work with a file, and we don’t know what kind of file it is (it can be a file on disk, the end of a pipe, or something else), but we want to write to this file. As in that example with widgets. See? There is polymorphism, but it is only where it is needed. And no"
The idea of ​​OOP seems extremely muddy to me. Even the need for monads in Haskell, it seems to me, is easier to justify. That's why monads are needed, and even why it's a good idea to write an entire program in monads, I understand. Why is OOP needed? A good answer to this question would be a blog post on "What are monads in Haskell and why are they needed", but only with OOP instead of monads.
You can answer "Yes, you're right, OOP is not necessary, more precisely, it is necessary, but only sometimes, it is not necessary to write the entire program in classes." And maybe even bring some arguments. So, this is not what I need right now. I am now looking for a job. And for this I want to understand the OOP style of writing programs. Even if it is wrong, let's say, from an academic point of view. But I want to really understand it. I want to understand how it is being done in business there now. This is what I need at work right now. So I ask you to write only arguments in favor of the OOP, but not against it. I do not want to arrange a holivar here on the topic "Is OOP necessary?" Holivar on the topic "What is the correct definition of OOP?" also please do not arrange. Well, there, "What did Alan Kay mean by OOP?" And so on. Let's understand OOP in the sense that
Okay, what do I want as an answer. A link to a blog post would be ideal. Where would be explained why you need to divide the program into objects. With examples. Let's say some typical task would be dealt with. Tetris, text editor. And it would be explained how to divide it into classes. And that as classes there were not only widgets. Why widgets are made by classes, for example, I already understood. And most importantly, to explain why the program needs to be written this way, in OOP, and not just functions, and why it is divided into classes in this way, and not in others. Just explanations in the style of "Let's write in OOP. Boar, boar, boar. It turned out" is not necessary, I want to understand why it was done in OOP.
Maybe you can recommend some books. But, again, those where the examples explained why OOP is needed.

Answer the question

In order to leave comments, you need to log in

7 answer(s)
M
Mercury13, 2017-06-20
@Mercury13

The task of OOP: 1) Localize object state changes (encapsulation); 2) to connect different data bricks through standard interfaces (polymorphism).
The simplest Tetris is not too big to be written in pure OOP.
But imagine, we are starting to establish custom joystick or keyboard controls. And then we have this code.

enum {
  BT_LEFT = 1,
  BT_RIGHT = 2,
  BT_ROTATE = 4,
  BT_SOFTDROP = 8,
  BT_HARDDROP = 16,
  BT_PAUSE = 32,
  BT_CONNECTED = 32768,   // бит, указывающий, что контроллер подключён
};
class Controller {  // интерфейс
public:
  virtual unsigned poll() const = 0;   // сочетание битов BT_XXX
  virtual ~Controller = default;
};

The Keyboard and Joystick classes support the Controller interface, and replacing the keyboard with a joystick and vice versa will not change anything.
That's polymorphism for you.
We turn the text editor into a multi-window one - we take the Editor class and attach it not to the program as a whole, but to the MDI window. That's encapsulation for you - a localized state change.
I somehow tormented the Doom engine. It is written in real object style in pure C! Although there were problems there too: the network code was much worse in quality than the engine itself. They wrote a split screen, the netgame global variable was divided into two, multiplayer and netgame, and bugs were corrected for a long, long time, where multiplayer, where netgame (there was a case, a desmatch participant entered IDKFA, it worked and caused desynchronization). And the user interface code is generally a bear!

D
Dmitry, 2017-06-20
@TrueBers

Long question, long answer:
And why do you deny static? Dynamic, for example, has vtable overhead, cache invalidations, and so on. If the STL used dynamic polymorphism, we wouldn't have this efficiency. And static has no such problems.
Encapsulation is not about privateand at all public. But rather about the interface and its hidden implementation.
www.ddj.com/cpp/184401197
About rendering widgets, in fact, the question is holistic. With widgets, initially we have a broken interface. That is, a Widget can add the Widget* or Widget& type to itself. And now we do this:

class Widget {
public:
  void add(shared_ptr<Widget> child);
};

auto root = make_shared<Widget>();
root->add(root); // о_О шта?

We can do this, which means the interface is incorrect. Nobody needs heaps of checks in the add() function. There is such a man Sean Parent, you may have heard, so he suggested replacing this problem with another data structure with static polymorphism, without unnecessary overheads. They use it in Photoshop. In what other language can this be done so efficiently?
But how do we call it in STL containers and everything works fine without inheritance and virtual functions?
Not the fact, depends on the optimizer and an amount of functions. An adequate optimizer deploys a switch in a jump-table, which can be a couple of percent faster than a virtual call. At the same time, in the same way, it can devirtualize a virtual function if it is used in an elementary basic block, for example, in a loop.
It is never required of him. It is enough to outline the adapter , the C ++ ideology in three, as you said, whales: containers, iterators and algorithms . Having written an adapter, it will work with all the standard library and algorithms, which is usually more than enough. For example, the same std::queue , std::priority_queue and std::stack are adapters and can change the vector (and not only) without inheriting from anyone.
Well, so, right. You are not always interviewed by geniuses. There are fools, nerds, idealists, holivarians, PMCs. Nothing good is usually expected from them. You just need to get away from those.
About virtual functions in C and other dragging of plus functions into this language. I think it's stupidity when you can just create a coding convention for plus code, and write it in C style, but at the same time use a lot of useful plus features. This is the correct approach, IMHO. And when the imitation of all this begins: I forgot to initialize, or somewhere, I don’t understand where, I initialized it with the wrong one until you find the problem, instead of just opening the constructor code.
In total, my answer is:
1. To my task - my owntool. Do not hammer nails with a microscope, you will need it intact for another.
2. Don't chill. If it’s “supposed” to be so, but it doesn’t fit into any framework, you don’t need to shove it further there. You need to step over pride and ideals, and do it differently.
3. Make the most of every paradigm and technology. Fortunately, in C ++ this is in bulk. Somewhere the functionality will trample more, somewhere it is better to pile up with templates. And where it didn’t work out, you have to shove crutches.

A
Armenian Radio, 2017-06-20
@gbg

You have an error in the first sentence. C++ is a multi-paradigm language. The program is built on it in a way that will be more beneficial from the point of view of architecture.
And STL is a great example of orthogonalization of containers, data and algorithms.

J
jcmvbkbc, 2017-06-20
@jcmvbkbc

I like the Linux kernel code. There is no division of the program into objects, and everything is okay there, they live perfectly without such a division.

Yes, yes, yes, https://lwn.net/talks/fosdem-kobject/
Yeah, but he wasn't always like that. Its current design is the result of an evolution. Evolution is caused by the desire to improve quality. To improve quality, you need to reduce complexity. OOP is one possible way to reduce complexity.
The book Design Patterns is perfect for this . (the scan, by the way, is not very good, an important piece of history is missing at the end). Just do not read about the patterns themselves. Read the rest: where did they come from, what is their history, how to use them.

A
Adamos, 2017-06-20
@Adamos

Really, maybe you haven't even read "Perfect Code"?
OOP is a unique opportunity to divide code into levels of abstraction. Implement everything boring, but necessary, inside the methods of the class - and forget everything about it, except for its interface. As a result, the program is written at a higher level and read at the same level. Details need to be studied only when it is really required.
Kilometer-long sheets of functions that can be called by anyone anywhere with unknown prepared inputs and equally unknown expectations for the output - well, you might like this ... if you have a lot of free time or you have spent half your life studying this particular chaos.

D
Denis Suprunenko, 2017-06-22
@wertex

As for inheritance and polymorphism, they are rarely needed.

But only after reading this you can understand that you did not understand the whole essence of OOP. I have one project (60Kb in the assembled firmware) for hardware for an ARM processor in C. The project has grown so much that I'm thinking of implementing the same thing in C ++ using OOP in the future.

V
Vjatcheslav3345, 2017-07-07
@Vjatcheslav3345

OOP is usually meant for very, very large and complex programs.
To understand why and when their developers use OOP, you can conduct a comparative analysis of complex object and non-object programs - for example, compare Linux with singularity ( singularity.codeplex.com/SourceControl/latest#veri... and Doom's code with a modern open engine and etc.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question