7 ms·
Sadly, this is representative of the new trend of C++ programmers. I will get down voted, hated, and laughed at, but I do prefer the original version of the cod
by Galanwe 11y ago
Sadly, this is representative of the new trend of C++ programmers.
I will get down voted, hated, and laughed at, but I do prefer the original version of the code.
It is infinitely more simple and readable, and the layers of abstraction added are of pretty much no value here.
If I stumbled across the latter version, I would just scratch my head. And I've seen sooo many projects just end up in a big code bloat just because some programmers wanted to add tons of "new" features like this that add nothing and just complexify the code for no reason.
- 72deluxe 11y agoThe second version is quite convoluted but it makes sense. But as you say, in this instance I don't think it is a problem that really needs to be fixed when the first one is quite obvious and simple to read. And to me, the first version does look like someone used to C who comes to write C++ but writes their C++ like it is C. Wrapping it in the second version (as others have pointed out) is safer because it is reusable, and cleans itself up.
- svalorzen 11y agoI don't understand how you can say that with a straight face. The ArrayView class is incredibly simple and takes about 10 seconds to understand. The "complex" stuff is in the make_contiguous code, which in the end is simply adding integers taking into account alignment requirements (which you still need to understand if you want to write the first version in the first place). And seriously, did you read the initial point? The problem is that the first approach must be fully rewritten for every new class with different members that exists in your code. Given how HN is butthurt over off-by-one and out-of-buffer errors, you can't say that the first example can really be debugged easily when you have 30 of them in your code. If you only had the Mesh class in your code and nothing else similar maybe it would not be worth it.. but only maybe. The final example is incredibly simple to use, hides the complexity in one single point and prevents you from ever making mistakes outside of it and seriously, it is not that complex.
- Galanwe 11y agoSorry, but again I have to disagree with you on every point. I may appear as an old grumpy programmer, but this kind of article exactly falls in what Zed Shaw calls "the expert", and I totally agree with him. (http://zedshaw.com/archive/the-master-the-expert-the-programmer/ http://zedshaw.com/archive/the-master-the-expert-the-program...) There are two 3 wrong premises in this article (IMHO): - It makes you think that using raw pointers (so called 'low level types' in the article) is more error prone than using combined types. - It makes you think that adding abstraction removes complexity. - Makes you think that you should combine/factor everything because repeating yourself is forbidden. These assertions are straight false. - Adding abstraction does NOT remove complexity. It ADDS complexity, and then HIDES it. The ArrayView class has absolutely no use whatsoever. Every C/C++ programmer is used to access arrays through pointers. Creating a class for that just makes a reader wonder "what the hell is this thing?". And then you have to crawl in the code to understand what is this thing. And of course, in 3/5 years from now, this ArrayView class will have tripled in size. For something as simple as reading numbers in memory, i don't want any abstraction. - There is nothing wrong in repeating yourself once on a while => if it improves readability! Factoring too much IS wrong. It makes reading the code a pain. When I read code, I don't want to read an abstract version of what the code does. I want to grasp the maximum possible information with the minimum noise. After reading the final version of this Mesh class, I have no idea of its memory layout. I see some ArrayView and a magical create_contiguous_memory function. I'm lost. I don't know where the memory come from, whether or not I own it, what will happen if I hand a pointer to it. Everything is hidden, and that's a pure pain to understand.
- svalorzen 11y agoIt seems to me that you simply don't like what C++ stands for, and that's perfectly OK; but then complaining that C++ follows C++ principles seems foolish to me. To quote the original article: > should we be using these low level types in this particular class, or use classes that better represent what we need and leave the low level types to them? There is no mention that we want to remove complexity. Instead, the aim of modern C++ is in fact to hide it, so that high level code can be written as simply as possible while hiding all the implementation details in classes. The code you see in this article is no different from the code you find in the implementation of the standard library; in fact since it is simpler it is even easier. Does not mean that the standard library does not have complexity - but it hides it from me, possibly with low expense. Also it never mentions pointers, the problem there is all the arithmetic which is not portable and highly prone to bugs should it ever need to be modified. The article is not factoring. It is coalescing a feature in a simple, single purpose class which does very little, and has a very simple use case and usage. If you have no idea of the memory layout, then you need to read the class, exactly as you would if you wanted to know the memory layout of std::list or std::vector. Making a class does not prevent the complexity from existing, it just prevents you making silly mistakes while using it. It never allows you not to know your trade and forget how to program. It seems to me you are a C guy. Really, I get what you are saying, it is just not the philosophy of C++, and if you disagree, maybe you shouldn't be using C++. That's perfectly fine, and I love that there are C people doing C things - it's not my cup of cake, but I'm happy they're there. And I would understand also if you were complaining about C++ trying to do something it is not. But what's happening here it really not the case, so I don't agree with you either.
- deleted 11y ago[deleted]
- jasode 11y ago> infinitely more simple and readable, It's simpler at the expense of being more bug prone. Notice that the "simpler" struct has no housekeeping code to "delete[]" the memory. The programmer now has to manually "manage" that cleanup somewhere. >, and the layers of abstraction added are of pretty much no value here. The "value" he's addressing from the "simpler" but more fragile code: 1) remove bug-prone code which requires manually synchronization (copy&paste) of pointer types in both the lines defining the variable and the subsequent lines casting the pointer from a raw buffer: "mesh->positions = (Vector3 )mesh->memory_block;" 2) remove brittle manual arithmetic of pointer offsets embedded inside contiguous arrays such as: "mesh->uvs = (Vector2 )(mesh->memory_block + positions_size + indices_size);" Calculating offsets (and/or enforcing logical boundaries) inside of arrays is very error prone and the OpenSSL Heartbleed bug was an example of that type of defect. 3) manage the release of memory using unique_ptr. Obviously, it is to help the programmer (and other coworkers using Mesh data structs) avoid memory leaks. >features like this that add nothing and just complexify the code for no reason. It doesn't look like "no reason" to me. The attempt was to reduce bugs, and add memory safety.
- peterashford 11y agoI agree with you. While I don't code c++ now it used to be my tool of choice (right back to when it was a c pre-processor) and every time I look in, the language is uglier and conceptually heavier. If there's two ways to solve a problem, c++ will provide three - none of which work on their own without gotchas, none which play nicely with each other and each which will be required for working with some 3rd party library you need.
- huhtenberg 11y ago> the new trend of C++ programmers Regrettably this trend is all but new. Boost is god knows how old now and just look how popular it is in certain circles.
- peterashford 11y agoQuite. It's been going on from the outset. Basically they got it wrong and they've been hacking at it ever since in a vain attempt to get it right. The should have killed the C linking compatibility from the outset (header files are devil spawn), they should have had a native string type as well. D represents a much better attempt at what C++ ought to have been.
- xedarius 11y agoI could not agree more with you. You would never write a class like that for a skinning system, it's full of bloat and template guff. Also at some point you're going to have to promote this to the GPU, at that point the original code will have a closer affinity to the GPU code rather the nonsense c++. I can forgive this though as he states he's not a games programmer.
- carlosrg 11y agoThe original version is terrible. C code written in C++. Basically an high level assembly language. Write all your code this way and soon you'll stumble with all the nastiness of manual memory management. Code like that is the main culprit of C++ is still considered an "unsafe" language, despite a correct usage of it, although more verbose and with more abstractions, brings both the benefits of C-like speed and higher level languages safety.
- gd1 11y ago>Write all your code this way and soon you'll stumble with all the nastiness of manual memory management. See, this is what I never quite understand. I use C++ when I want to do C type things. Manage my own memory. Bit twiddle with speed. Avoid all dynamic allocations. Increase cache coherency. Call inline assembler. Use the CRTP to achieve static polymorphism. Design my own containers. Lock free containers with atomics. Etc. If I'm using C++ I want nastiness. I want to get down and dirty. Why on earth would you ever want to use it as a higher level lanuage? It sucks balls at that. Just go and use a proper, nice, well designed high level language instead. If I want a language that holds my hand and protects me from the evil pointers, I'm not choosing C++. Using C# or Java (for example) is complete luxury compared to C++. But apparently, there are a whole bunch of people that do attempt to use C++ for this purpose. Who strive to achieve a language where you never see a raw pointer, but you are still stuck with maybe 30% of the productivity you'd have in a proper higher level language. It boggles my mind. Congratulations, now you have no garbage collection, a crap IDE, you're still wrestling with forward declarations of classes and header files and the preprocessor and linker options and slow compilation and you've now buried the machine under a layer of abstraction. The absolute worst of both worlds. >brings both the benefits of C-like speed and higher level languages safety. It doesn't. You end up with neither!
- IshKebab 11y ago> It doesn't. You end up with neither! Except that it does. This article clearly demonstrates one way that C++ provides higher level safety without overheads - the allocated memory is guaranteed to be freed. Nobody is saying you can't do low-level stuff in C++. They're just saying that C++ provides a zero-overhead way to do it safely. Why wouldn't you want that?
- royjacobs 11y agoThe current trend is for C++11 libraries to be header-only, lightweight and specifically without the hacks that have traditionally been required to work around all sorts of compiler issues.
- pczarn 11y agoThe original version is not only bug prone. In some scenarios, both versions have a potential vulnerability. Can you spot it? In a scenario where users can access elements of a joint array of requested size without limitations, they can access memory outside the array. Memory block's total requested size may overflow size_t. Fortunately, overflow prevention is easy to add to the generic version.
- Galanwe 11y ago> both versions have a potential vulnerability. Can you spot it? What the hell are you talking about? It's a game. > In a scenario where users can access elements of a joint array of requested size without limitations, they can access memory outside the array Welcome to modern computing! A process can access any part of its mapped address space without limitation. Joke aside, are you suggesting a process should hide memory from itself? Forget it, I can't even make sense out of your comment. > Memory block's total requested size may overflow size_t Are you serious? Then please suggest a fix for C, C++, Java, and whatsnot, because nothing will prevent you in pretty much any language to ask for more memory than you can handle. This is not a _bug_, it's just expecting the programmer not to fuck around.
- jsnell 11y agoExample case of where it matters would be modding. There's an expectation that installing things like new textures and new models is safe. Likewise for making changes using a game's internal scripting system. If these external resources aren't properly validated, they allow a apparently safe mod to execute arbitrary code. Doesn't matter that it's a game, it's bad either way.
- pczarn 11y agoI'm not talking about game rendering, but multiplayer games and game servers might be affected. I mean, the requested size must come from an external source. Here's an example with a better description: https://www.verisign.com/en_US/security-services/security-intelligence/vulnerability-reports/articles/index.xhtml?id=814 https://www.verisign.com/en_US/security-services/security-in... >(...) could allow an attacker to execute arbitrary code with the privileges of the current user. >The vulnerability occurs when reading the header of the Pack200 compressed Jar file during decompression. To calculate the size of a heap buffer, the code multiplies and adds several 32-bit integers. The bounds of these values are not checked, and the arithmetic operations can overflow. This results in an undersized buffer being allocated, which leads to a heap-based buffer overflow. I remember there was a similar bug in some other decompression library.
- humanrebar 11y agoThere is a third way... don't write ArrayView<> and put all the "C style" code in the constructor and destructor of a Mesh class. Then you get something you find intuitive and you get exception safety. Your comment that this is less readable is valid. Readability varies widely with background, skillset, consistency, and a host of other factors. If your team finds C-style code more intuitive, don't write Java style code. If your team finds malloc error prone, use smart pointers. Perhaps the biggest advantage of C++ is its flexibility, at least compared to the other mainstream languages. This means everyone can write code the way they want and others can use it (a). (a) OK. There is still a large list of gotchas around types, the preprocessor, header inclusions, namespacing, etc. that you need to know about to be a good citizen, at least until C++ can figure out how to support modules.
- quicknir 11y agoPost author here. Sorry you feel that way. I'm surprised you don't think that moving out the messy code into make_contiguous is a win. Isn't it good to factor out clearly reusable code into functions, so you can reuse it? Would you really prefer writing out code like that in a dozen places for a dozen classes that needed contiguous storage, to writing one function, testing it, and then calling it from all those other places?
- JoeAltmaier 11y agoThe 'functional people' are currently anti-reusable code. It gets in their way and complexifies their function graph. Which are bad things for them. As a lifetime C++ guy I find this new paradigm (inline code a-la structured code) brittle and fragile. App assumptions are laced throughout; nothing can be changed without a rewrite. And my go-to code-reading techniques (follow a class through all its incarnations with grep etc) fail - they rarely subclass, just overriding existing classes locally to create callbacks etc. So I'm not a fan of the new order. I see it's a productivity tool for the app writers out there. Especially with the one-off code that startups write, expecting to do a rewrite once they're funded. It really makes sense for them. But I weep for the craft.
- yassim 11y agoHi, Another Game dev here. Firstly, neat article, thanks for writing it. I'm curious as to the problem you're exploring/solving here? "Concatenated struct's in single chunk" is a neat trick. The use case seems a bit broken[1], but I'm assuming that it's from the original video, and your c++-ifying it? Or at least trying to find a nicer way to write this style of code? If you'r going this route, I'd be tempted not to use pointers at all, and just use offsets and accessors.[2] * Lack of pointers means you can load in 1 read. (endian needs to be watched, and assumes you can get the entire size elsewhere) * You can also shuffle it around in memory should you be doing something fancy. (defragging heaps might be useful if doing a sandbox) * Better encapsulation? maybe.. But the basic point I'd like to make is, there is no nice way to write this type of code, because c and c++ gives us no nice[3] way of expressing it. [1] Verts and Indices are generally uploaded to a GPU then discarded, but this has the counts, which is needed CPU side for draw calls, in the same chunk so would need to be copied before freeing. [2] struct Mesh { int32_t num_indices, num_verts; // inline to this struct.. // assumes Vector3 is aligned 4 bytes. // and that Mesh has been alloced to same alignment. // // Vector3 positions[num_verts]; // int32_t indices[num_indices]; // Vector2 uvs[num_verts]; inline const Vector3* PositionsBegin() const { return reinterpret_cast<const Vector3*>(this + 1); } inline const Vector3* PositionsEnd() const { return PositionsBegin() + num_verts; } inline const int32_t* IndicesBegin() const { return reinterpret_cast<const int32_t*>(PositionsEnd()); } inline const int32_t* IndicesEnd() const { return IndicesBegin() + num_indices; } // etc }; [3] Maintainable, fast at runtime, low mental friction to people other than the author, etc