Am I just a very bad coder or is this an abuse of C++ ?


  • I survived the hour long Uno hand

    Agilefall, I like to call it. Agile + waterfall = gracefully tumbling down a flight of stairs.



  • Agilefall? Hmmm, I think I've seen that in action myself from time to time...



  • @Yamikuronue said:

    Agilefall

    @tar said:

    Agilefall

    Jinx!



  • Or quickly tumbling down a hill for that matter.


  • Discourse touched me in a no-no place

    My advice: keep a few low-hanging fruit lollipops in your back pocket that you can pop in while you are actually doing the necessary maintenance grind. You don't need to tell people that they're trivial things; they're just little ideas that you can deploy rapidly to keep people off your back.

    And I've seen even good bosses back developers into a corner with a constant demand for new features that prevents doing proper maintenance. Doing the maintenance makes it much easier to put in the new features, or to know for sure that they're something that must not be done. All software should have both clear goals and clear non-goals, and management should definitely be told what the non-goals are.


  • Java Dev

    We're agile in theory. But the VP who introduced it is gone. And at the SVP level they're like? Agile? That's fun. Now, which features are you going to finish in the March 2016 release? Because we'll be wanting them code-complete in February and QA-complete in May.


  • Discourse touched me in a no-no place

    @PleegWat said:

    Now, which features are you going to finish in the March 2016 release? Because we'll be wanting them code-complete in February and QA-complete in May.

    QA-complete in May for a release the following March? :wtf: Or is the software being burned into hardware that is being shipped to space or something?



  • @CatcherInTheTry said:

    Oh, you must mean Never because, as in all corporate environments, that's all the time we'll get for refactoring anything.

    @CatcherInTheTry said:

    So true, and we've recently decided to implement "agile" as in that kind of agile

    Of course you never get time to refactor and produce no new features. But that's not how it's supposed to work anyway.

    The right way is to properly fix the code when you need to fix it. So from the management point of view, the work is simply part of implementation of the next feature.

    This is what extreme programming tells you to do and it's what you could take from it even if the management bits of agile don't work. You however need whole team in for it; if some developers are willing to hack it up in shorter time, you'll have hard time defending the larger estimates at the points you want to do things properly.

    It's also your responsibility to find balance between development time and quality. You need the code to be good enough to be easy to debug, not perfect. Then the overall project time should not suffer (because you'll reduce the number of obscure, hard-to-fix bugs) and your estimates will actually be more accurate (because you'll spend less time with unestimable bug fixing) which the management will appreciate. But then, the responsibility for writing shitty code is your either way, because management does not tell you to write shitty code.

    @dkf said:

    And I've seen even good bosses back developers into a corner with a constant demand for new features that prevents doing proper maintenance.

    Bosses need new features. You need features too. They are what makes money for the company and ultimately pays your bills. That's why you should do maintenance bit by bit as part of tasks that benefit from it.



  • @tar said:

    Now I'm wondering whether there are any situations where something like size_t c = vector.size(); is actually going to bite me on the as...

    For std::vector never. Because std::vector is required to hold the data in consecutive block of memory and size_t is required to be large enough to hold size of the largest memory block possibly obtainable.

    It is however possible with other types of containers. On i486 huge model pointers are 48-bit (16-bit segment id + 32-bit offset). The largest possible allocation is only 2³² , so size_t can legally be 32-bit, but the program can span multiple segments and can therefore allocate more memory for something like std::list or std::deque. The former has rather high overhead, so it's unlikely to exceed 2³² items, but the later is typically a list of pages and could exceed 2³² items. I haven't seen actual compiler for huge mode though so I don't know whether it actually used larger type for std::deque::size_type.

    You can also have larger size when using stxxl, but that does have the size_type as template parameter, so you know what you configure for you.



  • @Bulb said:

    You can also have larger size when using stxxl, but that does have the size_type as template parameter, so you know what you configure for you.

    That looks pretty interesting—bookmarked for if I do ever find myself running calculations on supercrazylarge datasets!



  • It can be also useful if the datasets are not that large, but need to be stored on disk for whatever reason and database is not a good fit.



  • That's a good point, I was idly wondering about that. Well then, it's directly relevant to my interests! :D


  • Java Dev

    I think that means QA must have done first test pass over all features. Not sure. In non-agile departments, there may be a 'convert-features-to-bugs' pattern going on.


  • Discourse touched me in a no-no place

    @PleegWat said:

    there may be a 'convert-features-to-bugs' pattern going on

    That sounds like some software developers I know…


  • Java Dev

    I think that pattern has been on the front page at some point in the past.



  • @RaceProUK said:

    In the case of Java, you're forced into handling them; at least C# makes it optional 😄

    Not all exceptions in Java are checked exceptions and only checked exceptions have to be caught.

    It'd be just silly if you had to have a try/catch block for NullPointerException... well... ever.


  • Discourse touched me in a no-no place

    @powerlord said:

    Not all exceptions in Java are checked exceptions and only checked exceptions have to be caught.

    I wasn't going to tell them. They've convinced themselves that they're superior and so aren't going to listen…



  • Here's a try-catch that will catch NPEs.

    try {
      ClassLoader pluginClassLoader = new PluginClassLoader(pluginJar);
      String mainClass = pluginDescription.getMainClass();
      Class<? extends JavaPlugin> pluginClass = pluginClassLoader.loadClass(mainClass);
      Constructor<? extends JavaPlugin> constructor = pluginClass.getConstructor(new Class[]{});
      JavaPlugin pluginInstance = constructor.newInstance();
      pluginInstance.onLoad();
      
      pluginMap.put(pluginDescription.getName(), pluginInstance);
    } catch (Throwable t) {
      log.throwing(t, "Plugin %s failed to load", pluginDescription.getName());
    }
    


  • @NeighborhoodButcher said:

    Jesus, I actually quoted myself so you could read it without searching. Let me do it again:

    You also said

    @NeighborhoodButcher said:

    It wasn't deprecated because it didn't do its job. We got a new tool and the old one couldn't be upgraded because of backwards compatibility.

    @NeighborhoodButcher said:

    No they're not. Raw pointers give you nothing - no info about ownership or lifespan.
    Here's my view. First, let's look at pointers that are a member of a class. In the case of auto_ptr, if you don't take special care in the copy constructor and assignment operators, your class is almost certainly buggy. In the case of raw pointers, if you don't take special care in the copy constructor and assignment operators, your class is almost certainly buggy buggy. In the case of raw pointers, you also have to deal with them in the destructor; I'll come back to that in a bit. That's by far the most common case of owning pointers, at least IME. In the case of locals and globals, usually you don't need to use pointers at all and can just talk about the object directly; the one exception is if you are constructing parts of an object and pass the pointers into some container later.

    I will admit that there are a couple extra errors that you can make in the case of the raw pointer, but I'd also argue that it's not as clear cut as that. In particular, deleting owned pointers in a destructor is the easy part of resource management; the hard part is figuring what to do when you copy the owning object, and you have to deal with that in both cases. Getting the destructor wrong is also almost always about the least damaging memory bug you can have. Then you also have to deal with non-owning pointers, at which point you're out of auto_ptr land and its semantics don't help you. Meanwhile, if you use auto_ptr you are much more likely to run into potential implementation deficiencies (such as a VS2008 stdlib bug that, IIRC, lets you construct auto_ptr<Foo> ap(new Bar()); where Foo and Bar are unrelated classes), you get surprising & nonobvious semantics, and you get what I say is a false sense of security.

    @NeighborhoodButcher said:

    I didn't actually understand that.
    I think you did, actually; your reply seems to indicate you got the gist of what I meant. Part of why you say

    @NeighborhoodButcher said:

    You always should look at base classes first, if there are any
    is because if you don't have an indication of what functions are inherited, that's what you have to do. If you have that indication, you have more options available. You could deliberately look at the subclass because you think that's where the functions of interest are located. Or maybe you often have to use the subclass, but because you see the indication of what is inherited your brain files that away and later, when you're working on something else, you remember that the function you need are likely available in the base.

    @CatcherInTheTry said:

    Virtual keyword is commented because that's how it is in the original source.
    Sure, but that just begs the question as to why it's like that in the original.

    (Maybe more replies as I catch up, finally?)



  • @dkf said:

    Most C++ authors seem to be extremely keen on avoiding exceptions, probably due to the legacy of truly awful implementations

    It's not just "legacy"; we switch off exceptions for most of our code base because even nowadays, with relatively recent compilers (i.e. well after they actually got good at supporting C++98) the compilation speed gained by disabling them is significant. They weren't being used much in our code base to begin with, and we are doing ugly things with templates and stuff that are already incredibly stressful to the compiler so the benefit is significant for us.

    (Note: I don't condone this really, just... reporting.)



  • I said it'd be silly, not that it was impossible.


  • area_pol

    @EvanED said:

    You also said

    Yes, I also said that. Also, as in addition to the reasons of deprecation, you seem to ignore.

    @EvanED said:

    In the case of auto_ptr, if you don't take special care in the copy constructor and assignment operators, your class is almost certainly buggy.

    Not buggy - you simply don't have those operators unless you write them yourself. And that's actually good, because auto_ptr, as unique_ptr now, expresses unique ownership, therefore copying a class makes no sense. How can you copy an object when one of its member is not copyable by design? Either a deep copy or no copy.

    @EvanED said:

    In the case of raw pointers, if you don't take special care in the copy constructor and assignment operators, your class is almost certainly buggy buggy. In the case of raw pointers, you also have to deal with them in the destructor; I'll come back to that in a bit. That's by far the most common case of owning pointers, at least IME.

    That's why you shouldn't use them - you never know what they point to and what to do with them.

    @EvanED said:

    I will admit that there are a couple extra errors that you can make in the case of the raw pointer, but I'd also argue that it's not as clear cut as that. In particular, deleting owned pointers in a destructor is the easy part of resource management;

    Unless another raw pointer points to the same data and both think they own it. Classic case of memory mismanagement, which cannot be automatically avoided on compiler level, as with smart pointers. I think that alone makes it clear not to use them ever. If you have no control of your memory, and in the case of raw pointers you don't, you can't do anything with them reliably. RAII is the way to go.

    @EvanED said:

    the hard part is figuring what to do when you copy the owning object, and you have to deal with that in both cases. Getting the destructor wrong is also almost always about the least damaging memory bug you can have.

    Yeah, undefined behavior and memory corruption are the least damaging for sure. I actually can't think of anything more damaging to the process. Undefined behavior means your process can legitimately download animal porn and set it as your desktop wallpaper, format your hard drive, email your boss how you hate him, but also go on without any errors or crash on the spot. There are even polls about such behavior in certain situations, like here: http://herbsutter.com/2014/12/01/a-quick-poll-about-order-of-evaluation/
    BTW. You should really get to know the works of this man. He's a great guy, very intelligent and knows more shit about C++ that you can imagine.

    @EvanED said:

    Then you also have to deal with non-owning pointers, at which point you're out of auto_ptr land and its semantics don't help you.

    Because auto_ptr (or unique_ptr now) is not copyable by design. In the old days, one would use boost smart pointers which can express shared ownership. Now we have those in std.

    @EvanED said:

    Meanwhile, if you use auto_ptr you are much more likely to run into potential implementation deficiencies (such as a VS2008 stdlib bug that, IIRC, lets you construct auto_ptr<Foo> ap(new Bar()); where Foo and Bar are unrelated classes

    That's the problem with shitty VS implementation, not with the class. The class had problems in its design, this wasn't one of them. Actually, I wonder what they screwed up, if this managed to compile.

    @EvanED said:

    If you have that indication, you have more options available.

    I can't see what more options you have there.

    • If you're a good programmer and you make good interfaces, everything you need is in the abstract interface.
    • If you need a concrete class for whatever reason, you use that class.

    Note that there isn't a mention of a commented virtual keyword anywhere. Why should there be? You either have stuff already in the interface or not. A comment doesn't change that.

    @EvanED said:

    Or maybe you often have to use the subclass, but because you see the indication of what is inherited your brain files that away and later, when you're working on something else, you remember that the function you need are likely available in the base.

    And that's exactly the backwards problem you're advocating. Don't look at subclasses first and you'll be fine. Do you seriously think we should look through concrete implementations to search for a functionality, and then optionally use the interface? That is extremely bad programming practice. If someone on my team ever did that, I would kick his ass with a cluebat.


  • area_pol

    @EvanED said:

    It's not just "legacy"; we switch off exceptions for most of our code base because even nowadays, with relatively recent compilers (i.e. well after they actually got good at supporting C++98) the compilation speed gained by disabling them is significant.

    Now that's an epic WTF. I understand disabling them for runtime performance, but for compilation speed? Also note that even with exceptions disabled, you still have them - you just can't catch them and the flow immediately jumps to std::terminate() in such case. Have fun debugging such issues. And yes, I know what I'm talking about because that's the shitty way Webkit manages things and Webkit bugs are hilarious (until you encounter one yourself).



  • @NeighborhoodButcher said:

    Not buggy - you simply don't have those operators unless you write them yourself. And that's actually good, because auto_ptr, as unique_ptr now, expresses unique ownership, therefore copying a class makes no sense. How can you copy an object when one of its member is not copyable by design? Either a deep copy or no copy.

    Yo, I'm real happy for you and I'mma let you finish, but I suspect that the auto_ptr fanclub is actually pretty small. In fact, at any company I've worked at where it's even been mentioned in the guidelines, it's usually in the context of "do not use auto_ptr". In a decade and a half I've never used it personally, which is not to say that it's not great, but it never really had much of a chance out in the broader C++ community...


  • area_pol

    Whoa there, don't mistake me for a fan of auto_ptr. I know how shitty it can be, but I also know literally anything is better than owning raw pointers. Unfortunately, the language wasn't quite ready to handle unique ownership back then. For some reason, the standard library didn't include any shared ownership solutions. That usually meant people went looking for help from boost. Now we don't have that problem. I wonder if there are other languages which support unique ownership the right way now as c++.



  • @NeighborhoodButcher said:

    I wonder if there are other languages which support unique ownership the right way now as c++.

    Someone is going to say Rust, so it may as well be me...


  • area_pol

    Have no experience with it. Is it any good or not worth my time to look at?



  • It's interesting. It has very strong concepts of 'ownership' and 'borrowing', so trying to write something equivalent to this in C++:

    int space;
    int *j = &space;
    int *k = &space;
    

    Would give you a compile error in Rust, because you can't have two references pointing at the same object in scope at the same time. (I mean, you can do that, but you'd have to write code to make that intent clear, they be Rc<int> (refcounted) pointers I think.)

    We did have a thread about it a few weeks back (in as much as any thread here is "about" anythiing...)

    http://what.thedailywtf.com/t/rust-discussion/7983

    It might be mentioned in this thread as well in comparison:

    http://what.thedailywtf.com/t/how-can-we-increase-adoption-of-c-c-alternatives/1870



  • @NeighborhoodButcher said:

    Yes, I also said that. Also, as in addition to the reasons of deprecation, you seem to ignore.
    Based on the fact that it immediately followed that, I assumed you were continuing the discussion of why auto_ptr was deprecated. If you weren't, I misinterpreted.

    @NeighborhoodButcher said:

    Not buggy - you simply don't have [a copy constructor & assignment operation] unless you write them yourself.
    No, you have compiler-provided implementations that are buggy in both cases. With a raw pointer you have a buggy implementation because a copy will alias the original; with an auto_ptr you have a buggy implementation because the "copied" object will probably have its desired invariants broken.

    @NeighborhoodButcher said:

    If you have no control of your memory, and in the case of raw pointers you don't, you can't do anything with them reliably. RAII is the way to go.
    I don't really dispute this point at all. I almost always use smart pointers in my personal code, and we use a refcounted pointer at my work.

    My point is just that I think auto_ptr specifically is bad enough that the gap between auto_ptr and raw pointer is pretty small.

    @NeighborhoodButcher said:

    Yeah, undefined behavior and memory corruption are the least damaging for sure
    Destructor bugs almost never lead to either of those; just leaks. If your destructor double frees or leaves a dangling pointer for example, that's not a destructor bug but a copy constructor/assignment bug.

    @NeighborhoodButcher said:

    There are even polls about such behavior in certain situations, like here: http://herbsutter.com/2014/12/01/a-quick-poll-about-order-of-evaluation/ BTW.
    You should really get to know the works of this man. He's a great guy, very intelligent and knows more shit about C++ that you can imagine.
    That link is really interesting. I wonder if I've seen it...

    Evan:

    @bdsoftware:

    Thus, the only two possible outputs would be 00 and 10. i don’t see how 01 could ever happen.

    That’s because you’re not thinking creatively enough. ...

    😉

    @NeighborhoodButcher said:

    Or maybe you often have to use the subclass, but because you see the indication of what is inherited your brain files that away and later, when you're working on something else, you remember that the function you need are likely available in the base.

    Don't look at subclasses first and you'll be fine. Do you seriously think we should look through concrete implementations to search for a functionality, and then optionally use the interface?
    That's not what I'm saying, at least in the part that you quote. At least on every code base I've worked on, there are plenty of concrete classes that you have to use. So, you'll see the APIs they provide quite a bit. If your view of those APIs indicates things that are inherited, it'll reinforce your knowledge about what functions are provided where.

    @NeighborhoodButcher said:

    Now that's an epic WTF. I understand disabling them for runtime performance, but for compilation speed?
    "Compilation speed" is probably a simplification; that happened a couple years ago and I wasn't paying too much attention for reasons that are not so important. It's entirely thinkable that it made the difference between compiling and not when building with 32-bit compilers. I could (but won't, sorry) write an article about some parts of our code base, but the relevant thing here is we have a code generator that produces C++ code that makes fairly heavy use of templates in a couple gigantic headers, and does a very good job of stressing compilers. It's not nearly this bad since things were improved some, but a few years back you could watch some compilation units take ~2 minutes to assemble.

    Our code base wasn't using exceptions very much anyway, so disabling them wasn't viewed as a huge loss by those making the decision. (Again... the purpose of this post isn't to judge, just report.) There are other factors that come into play too, but I think I'll pass on commenting on those.

    @NeighborhoodButcher said:

    Also note that even with exceptions disabled, you still have them - you just can't catch them and the flow immediately jumps to std::terminate() in such case.
    Sort of. Compilation units where exceptions are disabled can't generate them, which rules out exceptions originating from not just that compilation unit but any header-based library like most of the (non-C) C++ stdlib and Boost.

    You do have to worry about libraries that raise them, but even there we compile a lot with exceptions disabled. There are also some, uh, exceptions to our disabling policy, so if you really needed to call some library that uses them it would be possible to write a wrapper around it or just isolate the exception-dealing part of the code to one region.



  • Alright, this thread is now about C++ Great Masters...

    Did I miss anyone? ;)


  • area_pol

    @EvanED said:

    No, you have compiler-provided implementations that are buggy in both cases. With a raw pointer you have a buggy implementation because a copy will alias the original; with an auto_ptr you have a buggy implementation because the "copied" object will probably have its desired invariants broken.

    Technically, nothing is buggy here - you can't do copying by design and this has not changed. You cannot make a copy from const &. If compiler implicitly declares a & constructor, when dealing with auto_ptr, this can cause problems (this is one of which I mentioned earlier), but it's technically not a bug, since there was no other possible way of achieving transferring ownership. By following the rule of 3 in those days, this was a non-issue, since there was no implicitly declared constructors. In other words, if someone followed good practices, nothing bad could happen. If good practices were not followed, all it took was a single rvalue or lvalue which was cv-qualified, to raise an error. The chances of making mistakes were slim, especially with large codebases.
    Today we do not have those problems, since we have move semantics.

    @EvanED said:

    My point is just that I think auto_ptr specifically is bad enough that the gap between auto_ptr and raw pointer is pretty small.

    Your point is IMHO wrong for the reasons I stated previously. With raw pointers you have nothing in terms of memory management, with auto_ptr (and now unique_ptr) you have a way of expressing unique ownership with RAII support. auto_ptr only suffers from the lack of move semantics in old C++, but the benefits of automatic management and exception safety are strong enough to use it. If not, there's always boost. If not, you could write smart pointers yourself, as Webkit did, for example.

    @EvanED said:

    Destructor bugs almost never lead to either of those; just leaks.

    And that is wrong in 100%. Do a delete on a previously deleted pointer in a destructor and you'll see what is an undefined behavior.

    @EvanED said:

    That link is really interesting. I wonder if I've seen it...

    So you know Herb, good - learn from him.

    @EvanED said:

    That's not what I'm saying, at least in the part that you quote. At least on every code base I've worked on, there are plenty of concrete classes that you have to use. So, you'll see the APIs they provide quite a bit. If your view of those APIs indicates things that are inherited, it'll reinforce your knowledge about what functions are provided where.

    Again - you're advocating looking at concrete classes and then potentially moving up to interfaces. That is a very bad approach to OO and it pretty much violates the good old Liskov substitution principle, as you deal with leaf classes in mind first. You should look at bases. But if you really need a specific leaf class - just use it, since you require it anyway. In such case there is no need to look up the chain. You either require an abstraction or an implementation. If code requires looking at implementations first to derive a usable abstraction later, something is really, really wrong.

    @EvanED said:

    we have a code generator that produces C++ code that makes fairly heavy use of templates in a couple gigantic headers, and does a very good job of stressing compilers. It's not nearly this bad since things were improved some, but a few years back you could watch some compilation units take ~2 minutes to assemble.

    One project I worked on took over a day (as in 24h) to fully build and, believe me, nobody even considered ditching all the benefits of exceptions for compilation speed. Exception have their pros and cons, sure, but compilation is something I've never seen being remotely considered anywhere and I've seen some weird shit in my life. Runtime - yes and with merits, but compilation?

    @EvanED said:

    Sort of. Compilation units where exceptions are disabled can't generate them, which rules out exceptions originating from not just that compilation unit but any header-based library like most of the (non-C) C++ stdlib and Boost.

    You would need to literally recompile everything you use in order to disable exceptions, including std. I doubt it would work, since gcc tends to raise an error when you try using exceptions with -fno-exceptions, if I recall correctly. And all it takes is one library that throws, to make your whole codebase exception unsafe.





  • @NeighborhoodButcher said:

    One project I worked on took over a day (as in 24h) to fully build and, believe me, nobody even considered ditching all the benefits of exceptions for compilation speed. Exception have their pros and cons, sure, but compilation is something I've never seen being remotely considered anywhere and I've seen some weird shit in my life. Runtime - yes and with merits, but compilation?

    I suspect they were running the compiler out of memory....


  • Discourse touched me in a no-no place

    @tarunik said:

    I suspect they were running the compiler out of memory....

    How can having that sort of problem be anything but an indicator of something being vastly wrong. Either the input source is ridiculously large, or the compiler stupidly bloated. Or it's in C++ it seems.



  • @NeighborhoodButcher said:

    Technically, nothing is buggy here
    Technically you're right. But it almost may as well be, because you have a class that will become buggy if you blink at it wrong. It's as badly designed as auto_ptr is, which is "very."

    @NeighborhoodButcher said:

    auto_ptr only suffers from the lack of move semantics in old C++
    That's not how I would phrase it... I would say that auto_ptr's problem is that it has move semantics... just for operations for which move semantics are incorrect.

    @NeighborhoodButcher said:

    And that is wrong in 100%. Do a delete on a previously deleted pointer in a destructor and you'll see what is an undefined behavior.
    Again: the destructor isn't buggy, the bug just manifests in the destructor. There's a difference. The bug is whatever led two objects to both think they owned the same object -- and that's not the destructor that did that, it's the copy constructor/op=.

    @NeighborhoodButcher said:

    Again - you're advocating looking at concrete classes and then potentially moving up to interfaces.
    Again, I'm only "advocating" that a little, and I think advocating is even a strong word. My main point is that if you spend a lot of time looking at the concrete classes because that's what you need most of the time, but what you see includes information about inherited functions, you have an opportunity to internalize what you could get from the base if you had the opportunity.

    @NeighborhoodButcher said:

    And all it takes is one library that throws, to make your whole codebase exception unsafe.
    But from another perspective, only slightly more than usual (unless you use lots of catch(std::exception const & e) blocks). If we're using a library we can't compile without exceptions and we know it uses exceptions, either (1) we'll wrap it in a layer that swallows them and converts them to error codes or aborts or whatever it is we think is reasonable or (2) compile the code that uses it with exceptions. If we don't know if it uses exceptions then it could raise one behind our back, sure; but that's not so different from the fact that a library could raise an exception type that you don't know about. And that'll just lead to a terminate() call anyway.



  • @dkf said:

    Either the input source is ridiculously large
    Ding ding.

    Remember, I said that it's automatically generated C++, which is how it is so big. The biggest offender is a header that is north of 35,000 lines, and anything that picks up that will get another 10,000 line header and then another, I dunno, 15,000 across some others from the same broad group. (Don't ask how large our object files are...)

    The thing is though... I'm not exactly sure what I would do to break it up even if I had a couple weeks to work on improving the code generator and the inputs to that. A lot of that is because I'm not super familiar with everything that goes into it so I don't know what logical divisions are there, but it's very conceivable to me there really aren't any useful divisions. Maybe the thing to do would be to drop templates entirely and go to runtime polymorphism? But I don't know what that'd do to run times.

    So... I don't know what to think. The situation does kind of suck, but I'm also not totally sure what I'd do to make it better. (At least besides throwing out the whole approach and doing something completely different -- but I'm also not convinced that would, in the end, actually lead to something that worked better, and it would probably take a couple months to get the basic infrastructure in place and then who knows how many years of effort to move over the stuff we've built on top of it.)

    Edit: though maybe I ought to play with extern template at some point... I wonder how useful that would be for us.


    In our defense though, C++ compilers are pretty bad at dealing with templates. Even using some of the milder boost libraries for example will result in very strong upticks in compilation time.



  • Actually, @dkf made me really curious why things are as stressful as they are, because after a quick look, I don't really understand. I took a list of all the standard C++ headers and included them all in one file, and that preprocesses to a bit over 100,000 lines. <iostream> alone brings in about 24,000 lines. One of our largest object files is built from a preprocessed file with 400,000 lines.

    So our stuff is big volumewise, but not that big. So I'm not sure what exactly it is we are doing that is tough. (Now, obviously just including everything from the standard library won't instantiate any templates, and something in that process is lengthy, but now I'm not sure what.)


  • area_pol

    @EvanED said:

    Technically you're right. But it almost may as well be, because you have a class that will become buggy if you blink at it wrong. It's as badly designed as auto_ptr is, which is "very."

    If you followed the rule of 3, nothing bad was possible to happen.

    @EvanED said:

    That's not how I would phrase it... I would say that auto_ptr's problem is that it has move semantics... just for operations for which move semantics are incorrect.

    No it doesn't. It has source modifying copy semantics, which is the core issue. Notice how real move semantics in unique_ptr fixed all this.

    @EvanED said:

    Again: the destructor isn't buggy, the bug just manifests in the destructor. There's a difference.

    You said you don't have undefined behavior in destructor, and I showed how easy is to have one with raw pointers.

    @EvanED said:

    The bug is whatever led two objects to both think they owned the same object

    That's a strong argument against owning raw pointers, which is my point all along.

    @EvanED said:

    My main point is that if you spend a lot of time looking at the concrete classes because that's what you need most of the time

    I couldn't disagree more. Tying yourself to concrete implementations makes the code not extendable. You often pass concrete, but you take an interface.

    @EvanED said:

    If we're using a library we can't compile without exceptions and we know it uses exceptions, either (1) we'll wrap it in a layer that swallows them and converts them to error codes or aborts or whatever it is we think is reasonable or (2) compile the code that uses it with exceptions.

    That's a lot of work for no benefit IMHO.

    @EvanED said:

    that's not so different from the fact that a library could raise an exception type that you don't know about. And that'll just lead to a terminate() call anyway.

    Actually it's very different because with exceptions your stack would unwind. I don't think gcc does that with exceptions disabled. But that's something to look up.


  • Discourse touched me in a no-no place

    @EvanED said:

    In our defense though, C++ compilers are pretty bad at dealing with templates. Even using some of the milder boost libraries for example will result in very strong upticks in compilation time.

    What I said wasn't a criticism of you specifically, but rather of something in how the template mechanism works that seems to be particularly miserable. Or maybe it's just miserably implemented by all the significant compilers. (I'm not sure how to tell the difference.) I think there needs to be a major focus on improving this; it's getting to be a significant barrier to using C++ outside people already committed to using it, and it's got to be reducing the productivity of people using C++.

    For comparison, the full build of one of our Java product suites, including running all the automated tests and the construction of deployment packages for a bunch of platforms, takes around an hour on a system that's not particularly well provisioned. (It'd go one hell of a lot faster if we had SSDs for example.) If you go to scripting languages, the time from source to deployment package is even shorter. If you leave out testing, you can actually build the deployment packages on the fly when serving up an HTTP download. (A neat trick, but totally impossible with C++ as it currently stands.)



  • And that just feels... wrong to me.

    With Java, if you stick to Maven with straight compilation + packaging, no weird shit that has to O(n^2) your codebase, your compile time should be measured in tens of seconds.

    Go is in another league entirely with its compile times. "Ran 30 tests: 0.070s OK"


  • Java Dev

    Just depends on your java app, I guess. ADF environment, building changes and restarting weblogic so you can see them: 3-5 minutes.


  • Discourse touched me in a no-no place

    @riking said:

    With Java, if you stick to Maven with straight compilation + packaging, no weird shit that has to O(n^2) your codebase, your compile time should be measured in tens of seconds.

    The compiles themselves are trivially fast. It's the testing that really pushes the time up (we've rather a lot of unit tests and many of them need to hit the disk because of the nature of what is being tested), plus the maven build takes a lot longer if something has purged the repository cache and the flags of all the nations downloaded JREs for all supported platforms. It's also a pretty big and complex project with lots of automatic code generation.

    And we've not got SSDs on that system. We know they'd help a lot, but the build times are already pretty fast so it's not worth the bother.



  • @NeighborhoodButcher said:

    If you followed the rule of 3, nothing bad was possible to happen.
    If you follow the rule of 3, a raw-pointer-based class will also be correct.

    @NeighborhoodButcher said:

    It has source modifying copy semantics, which is the core issue.
    To my mind, that's an oxymoron, because if it modifies the source it, by definition, isn't a copy. In auto_ptr's case, it transfers ownership from the source to the destination -- and that to me is what the definition of "move semantics" is. Move constructors and std::move are a means to implement move semantics, but they are not necessary (and technically not sufficient, as you don't have to implement a move in a move constructor).

    And I'm not the only one who thinks this way. If I google "c++11 move semantics" (no quotes) the second hit for me is this SO question, with a continuation of the top answer saying things like

    To move an object means to transfer ownership of some resource it manages to another object. ... The dangerous thing about auto_ptr is that what syntactically looks like a copy is actually a move.

    (FWIW, the C++11 draft standard uses "move semantics" twice but never actually provides an official definition.)

    @NeighborhoodButcher said:

    You said you don't have undefined behavior in destructor, and I showed how easy is to have one with raw pointers.
    No, that's not what I said. I said you won't wind up with UB if you make an error in the destructor:

    @EvanED said:

    In particular, deleting owned pointers in a destructor is the easy part of resource management; the hard part is figuring what to do when you copy the owning object, and you have to deal with that in both cases. Getting the destructor wrong is also almost always about the least damaging memory bug you can have.

    (Sure, you can write a destructor that will say delete foo; delete foo; or something, but that's where the "almost always" comes into play.)

    @NeighborhoodButcher said:

    The bug is whatever led two objects to both think they owned the same object

    That's a strong argument against owning raw pointers, which is my point all along.
    Let me try to re-articulate. My contention is that, to a large extent, if your program has a heap mismanagement bug besides a leak with raw pointers, and you just did the same thing with auto_ptr at the sites that should theoretically be owning, it would probably still have a heap mismanagement bug.

    For example, if I have a pointer as a class member and forget to implement the assignment operator or copy constructor but pick up the destructor, then there is a (latent) use-after-free bug with raw pointers, but with auto_ptrs that same mistake leaves a latent null-pointer-dereference bug.

    You do get benefits from auto_ptr:

    • Protections from leaks
    • Protections from double frees
    • Converting use-after-free bugs to null-pointer-dereference bugs

    The latter two are far more valuable than the former (though I note that I suspect most of the time you have a double free potential you also have a use-after-free potential, so the third largely subsumes the second). In particular, during this debate I started thinking about the last one from an "exploit mitigation" standpoint, and this actually makes me warm to auto_ptr quite a bit. But there's also the drawback of the implicit move operation, the deprecation, and implementation deficiencies that narrow the gap some, and still make me say that the benefits are relatively minor when compared to the difference between raw pointers and an actually-good smart pointer class like those in Boost/TR1/C++11.

    Edit: In addition to all of the above, any "unique"-based pointer like auto_ptr or unique_ptr has the drawback that if you need to pass a non-owning pointer or reference to that object somewhere, you're back in the world of raw pointers, so a non ref-counted smart pointer still leaves open a lot of potential for memory errors with objects they manage, which is another reason that I'm down on auto_ptr.

    @NeighborhoodButcher said:

    Actually it's very different because with exceptions your stack would unwind.
    If the program's going to die anyway, the unwound stack won't usually matter.



  • @tar said:

    If use of .... in 'reasonable looking code' can cause crashes, it's not really 'usable'

    Hey, I can put a semicolon in a place that looks reasonable and then the program crashes because of it.....ergo semicolons are not usable in C++ and need to be removed/deprecated.



  • @TheCPUWizard said:

    Hey, I can put a semicolon in a place that looks reasonable and then the program crashes because of it...

    OK then, show us an example of that. Listings or it didn't happen...


  • BINNED

    Good day sir, may I interest you in JavaScript?


    Filed under: ASI is awesome!, </sarcasm>, just in case


  • Discourse touched me in a no-no place

    @TheCPUWizard said:

    Hey, I can put a semicolon in a place that looks reasonable and then the program crashes because of it

    Bonus points if you can make the program that crashes be the compiler. 😃
    Double bonus points if the debugger crashes when examining the crash dump.


  • area_pol

    @EvanED said:

    If you follow the rule of 3, a raw-pointer-based class will also be correct.

    No, because there would not be any indication of the ownership type. Every time you'd see a pointer, you'd have to dig through the code to find if you're the one responsible of it. No rule of 3 (or 5 now) would save you from it.

    @EvanED said:

    To my mind, that's an oxymoron, because if it modifies the source it, by definition, isn't a copy.

    That's what I was trying to emphasize. Per Standard, it was a copy operation, but it was a source modifying copy, yet it wasn't a formal move, since those did not exist. That is exactly what we both perceive as the problem - a copy, which is not really a copy. The distinction between this and the real move semantics is crucial, since if you look at it from C++11 perspective, they are anything but equal. Logically, it was moving ownership, on that we agree, and it adds to the mess.

    @EvanED said:

    (FWIW, the C++11 draft standard uses "move semantics" twice but never actually provides an official definition.)

    I took a look at the C++11 Standard now (final, not draft) and you have it pretty much explained in 12.8, which should be read with rvalue references definition in mind. There's not a definition of literal "move semantics", but another topic here has already shown how not to perform this kind of searches.

    @EvanED said:

    No, that's not what I said. I said you won't wind up with UB if you make an error in the destructor

    I know and I showed how me make a trivial error in the destructor which leads to UB - double delete - the most classic case that comes to mind. With raw pointers, it's more than possible. If you don't see it as a problem, well, let's leave it at that.

    @EvanED said:

    Let me try to re-articulate. My contention is that, to a large extent, if your program has a heap mismanagement bug besides a leak with raw pointers, and you just did the same thing with auto_ptr at the sites that should theoretically be owning, it would probably still have a heap mismanagement bug.

    You won't have double delete with auto_ptr. You might accidentally transfer ownership somewhere else, but the "loosing" side won't delete it again.

    @EvanED said:

    For example, if I have a pointer as a class member and forget to implement the assignment operator or copy constructor but pick up the destructor, then there is a (latent) use-after-free bug with raw pointers, but with auto_ptrs that same mistake leaves a latent null-pointer-dereference bug.

    That's a very good example IMHO. Dereferencing null pointers actually is a special case on OS level and (sometimes) MMU level (a bit oversimplifying here, because you can configure your OS to allow it for some reason). Dereferencing a null pointer will cause a GPF and therefore a crash, which is easy to debug. UB doesn't give you that, since control can go wherever. I would gladly trade every UB for a null pointer problem.
    Also - by following the rule of 3 or simply declaring an auto_ptr const, this problem ceases to exist. With raw pointers you cannot enforce that.

    @EvanED said:

    But there's also the drawback of the implicit move operation, the deprecation, and implementation deficiencies that narrow the gap some, and still make me say that the benefits are relatively minor when compared to the difference between raw pointers and an actually-good smart pointer class like those in Boost/TR1/C++11.

    I agree those are problems. But in my years of experience, I've found them to be negligible if good practices are followed. Raw pointers do not offer any enforcement. Thanks to C++11, we have something that's devoid of those deficiencies.

    @EvanED said:

    In addition to all of the above, any "unique"-based pointer like auto_ptr or unique_ptr has the drawback that if you need to pass a non-owning pointer or reference to that object somewhere, you're back in the world of raw pointers, so a non ref-counted smart pointer still leaves open a lot of potential for memory errors with objects they manage

    If you need to pass such pointer, you might be using a wrong tool for the job. If you have no control over the lifetime of the entity requesting the pointer without the need of ownership, you cannot have a unique_ptr, but a shared pointer. This is logical since you are sharing your data with something which might need it at some point in time, which in turn transforms the ownership type to shared. You can then use a shared_ptr, or weak_ptr if the requesting entity allows the pointer to expire. If you have control over its lifetime, you can use a non-owning raw pointer. There's nothing wrong with them when the assumption is they never own data and their lifetime is strictly contained within the lifetime of owning entity.

    @EvanED said:

    If the program's going to die anyway, the unwound stack won't usually matter.

    Especially, when dealing with external resources, right?


  • Discourse touched me in a no-no place

    @NeighborhoodButcher said:

    If you have control over its lifetime, you can use a non-owning raw pointer. There's nothing wrong with them when the assumption is they never own data and their lifetime is strictly contained within the lifetime of owning entity.

    A good example of doing this is if you're splitting a long string into a list of words. With a long string, you'd expect to have quite a lot of duplication, so you want to share the references in the resulting list (with experience, this is a big saving on multi-megabyte documents). Using a hash table which doesn't assert ownership is a good way to handle this since you can avoid having to do lots of work on handling shared references when you've got a guaranteed owner (the resulting list) that handles it all for you; after splitting you can get rid of the helper structures very quickly.

    It sounds trivial, and like it ought to be expensive because of all the extra hashing you're doing, but it turns out to be a superb algorithmic boost because it saves so much memory. It's a good example of why merely guessing why something is better or worse is difficult; measure it, prove it!



  • @dkf said:

    Double bonus points if the debugger crashes when examining the crash dump.

    Been there, seen that...on the Tandem NonStop, no less ;)

    Filed under: INSPECT, you know it's bad when the debugger crashes because the symbol resolution library has a path-handling buffer overrun bug in it


Log in to reply