Comments by "MrAbrazildo" (@MrAbrazildo) on "Low Level"
channel.
-
4
-
2
-
2
-
2
-
2
-
1:18, prove why Java is easier. Garbage collector is not a reason, since it's bad, clunk, not elegant, and I believe slower than destructor.
1:38, it's half the C++ performance. And C++ can be very defensive, if you code it for that.
2:24, pointless. C++:
auto s = "Low Level...";
int32_t x = 5, *p = &x;
And C++20 has native optional strong type checking, via concepts. Templates were never the issue, even back to 1998, if you use some smart tags.
1
-
1
-
5:53, I use to not include in advance. I wait for a compiler error ou the actual use of something by the lib. This way I can avoid unnecessary includes.
6:00, Const Correctness Principle: 1st write 'const', only thinking after a compile error. Thinking is a waste of energy, avoid doing it.
And whenever I write { } for a f(), I write its 'return' right away. So I avoid the missing return UB bug. On modern C++, a more consistent defensive method for that is to declare the returning type as 'auto'.
6:26, A << B is the highest level possible thing: B is being threw to A.
7:26, for a 1st approach, I think assert is better here: 1 line/cmd. I know it doesn't close files, but this is the 1st, and not opened.
7:33, you are throwing away 1 of the best features from C++: get rid of things automatically.
7:35, for apps, I use namespace std, to be more productive.
10:30, const auto ptr = std::find_if (new_begin, line.cend(), ::isdigit);
if (ptr == line.cend()) break; // There's no digit to the right from where the find started.
leftmost = *ptr - '0'; // '0' keeps portability. Don't use a number: unworth to be memorized.
new_begin = ptr + 1;
10:59, atoi works with char *, not char.
16:35, std::map does this. I use to implement this as 2 std::array.
16:42, this could had been just: if (gTable[i].str == slice). If str and num where 2 std::arrays, the whole f() could be:
const auto ptr = std::find (str.cbegin(), str.cend(), slice);
if (ptr == str.cend()) return -1; return num[std::distance (str.cbegin(), ptr)];
1
-
1
-
1:27, you could get rid of that label and its goto, by changing this to else.
Nowadays, C can get rid of resources automatically. Why didn't you use it?
2:00, if you want to use dangerous literal numbers, at least create a global constant: 0x_4545.
a) If a typo occurs, compile error. b) If that number ever changes, you can detect all of its occurrences by the compiler, not needing to rely on code editor features.
Another thing: all sorts of fields are being accessed in what be was a 'char *'. If that recv function (1:10) failed to fill 'databuf', all sorts of runtime errors would occur. Could recv fail?
2:30, no check for h->len, huh? Hm, I know to where this is going to... 7:10, yep, I knew it. However, I would write this as:
memcpy (msgbuf, data, h->len > 64 ? 64 : h->len); //At least it would get what could fit in it.
Whenever I mistake something like this, I write my own f(), like memcpychk, which would compare size of both arrays forever (at least if NDEBUG wasn't #defined). C++ provides additional security, by allowing to give same names. So it'd require namespace specification: ::memcpy for C's or my_own_namespace::memcpy for yours.
1
-
0:02, pretty? Signed var is faster.
0:10, in C++ it's possible to make a class that always implicitly checks if the pointer is nullptr.
1:17, I would not use linked lists neither, which rarely are faster, can't be used in STL algorithms, and forces you to make raw loops. For instance, if 'e' was a std::vector, this whole f() would be dismissed:
const auto Result = [](const std::vector <int> &e, const int search) {
const auto Result = std::find (e.cbegin(), e.cend(), search);
return Result == e.cend() ? null : *Result;
} (e, search);
1:24, C++ containers use to have iterators delimiting begin and end, putting the programmer in a range loop by default. For instance, std::forward_list performes a linked list in 1 direction, and has begin/end f()s to give those iterators.
1:55, or just use std::vector, which will free the memory, if its object no longer exists. It hasn't the use after free protection, but it's possible to wrap it in a class, to check that automatically.
3:20, but if that variable must travel alonside f()s, as read-only, and be changed only at the Nth f() called, C can't protect it. C++ has the solution: hide it in a class, making that f() friend of it, so that only it'll be allowed to change the variable.
4:01, C++ has the attribute [[nodiscard]], meaning that a compile error will raise, if the return value is not treated.
5:40, I always use pedantic, because it has good rules. But Werror forbids me to run the app. I always end up cleaning all warnings I turned on, but not always I want to do it right away, which may be less productive. Same thing for implicit conversions. So I don't turn on all warnings.
So we can realize that using C++ is a big improvement for defensive code, at least over C.
1
-
0:00, "....Nice day for fishing, ain't it?"
I made several tests of switch (different versions) vs if-block. Switches w/o default cases, w/o a case for numbers < and w/o number > than some value, and so on. I found that switches have a more unstable performance, some faster, others slower.
Array use to be faster than switch, by ~15% according to my tests. I remember only 1 combination of switch beating 1 array alg. So, it's better to rewrite this as:
const auto Options = "qcned";
const auto ptr = strchr (Options, optionbuf[0]);
if (!ptr) break; //Default case of switch.
#define _(Task) handle##Task
const auto funcs[] = { _(Stop), _(Continue), _(New), _(Edit), _(Delete) };
(funcs[ptr - Options]) ( ); //Cases are solved on the indexing. Function called right away.
1
-
0:01, when I code, it's always gorgeous! :elbowcough:
0:08, I disagree. Modern C++ features are higher level ones, easier to use and less prone to errors. It's becoming harder to get things wrong with it! The price is raising chances to write slow-prone code.
0:55, TABs are better: uses 1 char and can be configured equally for all the team. I use TABs with size == 2. 1:05, neither draconian nor pedantic: the team must has the same rule about it, because it's an annoyance to see messed indentations. Once the team agrees about its size, use TABs. 1:20, are you saying that if TAB has the same size, it still may differ from 1 code editor to another?!
2:37, I can agree that, once the f() is done, a typedef is the better returning type. However, during its development, auto is much better:
- More productive: it doesn't require to often change its type.
- Defensive: if forget to return, it'll be deduced as a void value, raising compile warning if attributed to some variable later.
5:25, I don't use to have this "diamond problem". Mother and Father are not the same person, so each of them should inherit its own exclusive object of Person. Child is yet another person, not tied to parents. So it should inherit Person as well.
I use to inherit classes when the derived 1 should has the power of change data of its bases. Except for Person, it's not the case. In real life, a child can change parents behavior by communication, not mental/physical interoperability. So Child should not inherit parents. Instead, its constructors should receive parents objects as 'const &', read-only stuff. Just as in real life: child receives a read-only genetic material from its parents, and is destined to be an independent person.
6:30, interface is too slow and just a bit higher level than a normal class. I don't use it, it's not worth. Plus, for those who use it, a better approach than an abstract class is to make a macro out of it. Then, by composition, call the macro inside the "derived" class, making it a normal class, 15-17x faster!
6:38, if a class has this limitation, I just make its constructors as protected, forcing inheritance.
1
-
I had 2 major uses for goto. 1 of them is what you are showing, that I call it "f() destructor". When I moved to C++, I hadn't this need anymore. The other case was to jump out from the middle of nested loops. But after lambdas (C++11), I stopped using goto at all.
For maintainability, I would also pack this code with macros:
#define errchk(var, value, err) \
if (var == value) { \
perror (err); \
ret_val = EXIT_FAILURE; \
goto main_destructor; \
}
int main (const int argc, const char *argv[]) {
void *filebuf; int fd, sfd, ret_val = 0;
fd = open (BLABLABLA); errchk (fd, -1, "open")
filebuf = malloc (BLABLABLA); errchk (filebuf, nullptr, "malloc")
sfd = socket (BLABLABLA); errchk (sfd, -1, "socket")
//Do all the stuff...
ret_val = EXIT_SUCCESS;
main_destructor:
if (fd != -1) close (fd);
if (filebuf) free (filebuf);
if (sfd != -1) close (sfd);
return ret_val;
}
#undef errchk
1
-
1
-
1
-
1