Back to Basics: C++ API Design - Jason Turner - CppCon 2022

แชร์
ฝัง
  • เผยแพร่เมื่อ 21 พ.ย. 2024

ความคิดเห็น • 99

  • @fredhair
    @fredhair ปีที่แล้ว +73

    Jason has probably done more for my understanding of good compile time safety than most, always full of good logical points and I generally learn a lot when he's speaking.

    • @jaybee9054
      @jaybee9054 ปีที่แล้ว

      Putting the fun back in CPP, for sure!

  • @bryancoxwell5827
    @bryancoxwell5827 ปีที่แล้ว +2

    I do not know a single thing about C++ and still found this to be an excellent talk.

  • @TheClonerx
    @TheClonerx ปีที่แล้ว +114

    Poor camera guy

    • @joestevenson5568
      @joestevenson5568 7 หลายเดือนก่อน +3

      He knew what he was in for when he found out it was a Jason Turner talk

  • @zahash1045
    @zahash1045 ปีที่แล้ว +73

    Video starts at 3:46

  • @RayaneCTX
    @RayaneCTX ปีที่แล้ว +39

    The title is a bit misleading. I think the presentation is more appropriately named as "C++ best practices for writing public APIs" (appeal aside). However, taken for what it is, this is such a good presentation.

  • @itorrestp
    @itorrestp ปีที่แล้ว +1

    Nice to see a conference with people interaction.

  • @intvnut
    @intvnut ปีที่แล้ว +1

    Regarding the very end of the talk: fuzzing tools are definitely and important part of your arsenal. I earned my Knuth Hex Dollar with the help of a fuzzing tool.

  • @alidanish6303
    @alidanish6303 ปีที่แล้ว +11

    I understand that Jason probably has lot to cover in the interactive session but since the discussion is about API design and error handling is fundamental part I just wish little more time where noexcept/ exception vs errno/ errorcodes best practices are discussed. Secondly, the person trying to explain error codes is somewhat convoluted, so we can have platform or lib specific error codes and then reaction in design can be based on the std::error_condition(platform independent).

  • @cppevents
    @cppevents ปีที่แล้ว +9

    Always love hearing Jason's talks. 👍

  • @AhmedSam
    @AhmedSam ปีที่แล้ว +1

    The only speaker that Ii love watching his seminars eating popcorn and laughing like I'm in cinema. Learn with func :D

  • @robertjmccabe
    @robertjmccabe ปีที่แล้ว +2

    I like the style of this guy's talks.

  • @_Omni
    @_Omni ปีที่แล้ว +16

    Jason is the best 👏

  • @prateekpatil4845
    @prateekpatil4845 ปีที่แล้ว +1

    "Pit of Success" is fairly old but a very prominent talk with that as a theme was Scott Meyers talk "The most important design guideline"

  • @ArtoPekkanen
    @ArtoPekkanen 7 หลายเดือนก่อน

    Make all containers delete copy/copy-assign constructors, use static constructor functions that return "value or error" semantics object like std::expected with no discard. Make all copy operations explicit function calls that construct std::expected style object in place and return it to be assigned by nothrow move/move-assign. The returned "value or error" object should be switched to figure out if it has value or error, or error with what value. Boom, no need for exceptions, not even for constructors since they should be non-throwing.

  • @seancpp
    @seancpp ปีที่แล้ว +1

    Oooh yes a new talk from Jason? Time to grab some popcorn

  • @haxpor
    @haxpor ปีที่แล้ว +7

    It looks to me C++ has complexity ahead of its time. std::expected (c++23) is Result in Rust that has long been there making code totally clean and clear in handling error. Thanks for video!

  • @dyazmovies
    @dyazmovies ปีที่แล้ว +2

    Great presentation, very insightful. Promoting those books right before Christmas was a smart move :)

  • @Dth091
    @Dth091 ปีที่แล้ว +8

    14:15 haven't checked the standards wording but both GCC and Clang trunk work like you'd expect with [[nodiscard]] on enum class/struct declarations.

    • @icestormfr
      @icestormfr 7 หลายเดือนก่อน

      Enumerations were not in the original proposals/working drafts, but in the current/latest revision it is included.
      See also [[nodiscard]] on cppreference website and the P0189R1 (C++17 features, "Wording for [[nodiscard]] attribute.") where it says "class or enumeration".
      In revision R0 (P0189R0) neither class nor enumeration was mentioned (same for P0068R0)

  • @xealit
    @xealit 4 หลายเดือนก่อน

    I like how C++ folks throw shade on POSIX for "all over the map" APIs, when in C++ you have to write function declarations a mile long, like one of the questions said :D

  • @MsHofmannsJut
    @MsHofmannsJut ปีที่แล้ว +16

    It’s sad that many things that work great as opt-out defaults are opt-in optionals in C++. So much history.

    • @BGFutureBG
      @BGFutureBG ปีที่แล้ว +6

      exactly, leads to all this keyword mayhem on declarations

    • @charactername263
      @charactername263 หลายเดือนก่อน

      Feels like perhaps there should be compiler flags to turn the opt-in optionals into opt-out defaults.

  • @KX36
    @KX36 ปีที่แล้ว

    the answer to the question "what should we do with this API?" is, as all answers to similar questions are, "ship it."

  • @xealit
    @xealit 4 หลายเดือนก่อน

    35:00 on the std::error_code "aside of it's a little weird, a little awkward, you get used to it and then it's easy" that's C++ in the nutshell

  • @MichaelLauerDr
    @MichaelLauerDr 7 หลายเดือนก่อน

    Excellent talk. I learned something today.

  • @multiHappyHacker
    @multiHappyHacker ปีที่แล้ว +2

    I kind of want to buy one of those puzzler books now.

  • @jplflyer
    @jplflyer ปีที่แล้ว

    As always, a great talk, Jason. Thank you.

  • @potter2806
    @potter2806 ปีที่แล้ว

    We've made lots of tools, and the hardest thing ever is the correct usage of tools.

    • @meanmole3212
      @meanmole3212 ปีที่แล้ว

      And that's because the language itself is a mess that allows you to do all the stupid things you can imagine unless you sugarcoat your code with esoteric keyword noise, and it's hard to believe doing even that would guarantee reasonable protection.

  • @johnmcleodvii
    @johnmcleodvii ปีที่แล้ว +9

    I've had a minor problem with a parameter to fseek destroy 2 hard disks. Fseek takes a parameter describing where to seek from (beginning, end, or current position) and a distance to seek specified as a signed long. Seeking outside of the bounds of the file and then reading or writing has undefined behavior. My code has 2 unsigned shorts that had to be multiplied to get the position from the start of the file to write the block of information. Unfortunately I forgot to cast the shorts before the multiplication and ended up with a 1 in the MSb of the resulting unsigned short. This was then sign extended into long that was used to seek from the start of the file. Unfortunately on that computer with that OS, and that hard disk this overwrote part of the engineering sectors of the hard disk. The manufacturer declared the HD was unfixable. The second drive was destroyed during single step debugging to find the bug and I went one step too far.

  • @jaybee9054
    @jaybee9054 ปีที่แล้ว

    Viewing Jason's CppCon talks from older to recent I wonder what's the ultimate objective for the beard..?
    🤣🤣🤣

  • @JosefdeJoanelli
    @JosefdeJoanelli ปีที่แล้ว +8

    I had no idea you could delete any old member function. Without being able to use auto parameters, I wonder if you can write a deleted templated function and then write a specialisation with its explicit parameter types that you want to persist?

    • @gnolex86
      @gnolex86 ปีที่แล้ว +3

      Yes, you can define a function template as deleted and then define specializations to be used. You can even use this to split declarations and definitions to separate files and allow users of your code to define specializations for their own types. Be aware that explicit specializations of function templates have somewhat non-intuitive rules when it comes to picking which specialization is used by the code and you could very easily make your life a debugging nightmare if you're not careful.

  • @stephenjames2951
    @stephenjames2951 ปีที่แล้ว +1

    Dynamic and interesting speaker.

  • @artemp.2122
    @artemp.2122 ปีที่แล้ว

    58:30. May be compile time regexp check can be used to verify mode? Assuming that it remains of string-like type

  • @ZarviroffSerge
    @ZarviroffSerge ปีที่แล้ว +1

    The vector empty() const. A verb? On a const? ahahaha)) This is a good talk.

  • @felixbors7546
    @felixbors7546 7 หลายเดือนก่อน +1

    Moving up and down does little for the online people.

  • @artemp.2122
    @artemp.2122 ปีที่แล้ว +1

    51:05. With WidgetType we lose an advantage of factory idiom that allows user to do not know concrete type to be created (forward declaration included). Or do i miss something?

    • @IsmeGenius
      @IsmeGenius ปีที่แล้ว

      Kinda of a late answer. But yes, you lose this "advantage". But the first API had WidgetType as an enum (or int), so you conceptually did know the type, you just didn't know the C++ type. And function returned a base instance you can't do anything with and will probably cast down anyway. Sometimes it is truly beneficial to caller not to know anything about returned type, but that includes a conceptual type as well, you pass parameters to a factory function (which is often also virtual), which would make sense for any type of returned value.

  • @rainbowkennyHSJ
    @rainbowkennyHSJ ปีที่แล้ว

    I don't understand the second code snippet of the factory method at 51:19. It doesn't take a int of widget_type as a parameter. I guess you will need to have some sort of switch-case matching against the enum inside the function to generate objects of WidgetType. What if an out-of-bound widget_type is passed?

  • @zxuiji
    @zxuiji ปีที่แล้ว +1

    45:47, For the swapable strings thing a simple solution would be to just add a regex compiler attribute that can be used on string parameters, something like:
    #define PATH_ATTR __attribute__((regex("^([A-Za-z]\\:)?...")))
    #define FOPEN_MODE_ATTTR __attribute((regex("^[rwx]+\\+?$")))
    FILE *fopen( PATH_ATTR char *path, FOPEN_MODE_ATTR char * mode );
    Sure it's not built into the C/C++ language but it works as a simple fix that can be used in both languages

  • @zxuiji
    @zxuiji ปีที่แล้ว

    Doesn't matter if systems use their own bitfields, you just need conversion functions to switch from their bitfield to the library bitfield, I'm doing that for my graphics library wrappers, I can then just grab the raw bitfield before entering the main loop for what I want to always use and use the raw passthrough of the same function, saves time while I always have the option of using the wrapper function for more simplicity, e.g.
    int clear_bits = pawvfx_get_clear_bits( PAWVFX_O_COLOR | PAWVFX_O_DEPTH );
    while ( ... )
    {
    // raw version of pawvfx_clear_bits which is just a thin wrapper around this and the above call
    pawgfx_clear_bits( clear_bits );
    ...
    }

    • @Milan_Openfeint
      @Milan_Openfeint ปีที่แล้ว +1

      Shouldn't you make a "ClearBits" type instead of converting int to int? Easy to forget to convert.
      Wouldn't PawVfx:: be better than pawvfx_ ? People can shorten it with "using" if they want to, plus code hinting works better.

  • @lucasrangit
    @lucasrangit ปีที่แล้ว +1

    At 58:32 what's the correct option for stringly typed OS dependent parameters?

    • @hpesoj00
      @hpesoj00 9 หลายเดือนก่อน

      Probably force the string to be wrapped in a strong type.

  • @smellytaint
    @smellytaint ปีที่แล้ว

    13:36 what would be the point of having a nodiscard enum??? Enums are just used to create symbols that hide magic numbers in the code right? So why would anyone want to have an enum that is nodiscard????

  • @BGFutureBG
    @BGFutureBG ปีที่แล้ว +1

    What's a "DSL" that he so often talks about? 🤔

    • @CHR73TANGO
      @CHR73TANGO ปีที่แล้ว +1

      domain specific language, en.wikipedia.org/wiki/Domain-specific_language

  • @AngeloMastroberardino
    @AngeloMastroberardino ปีที่แล้ว

    (maybe naive) try-catch question.
    I tend to wrap most of my function's code in bug try-catch block, and usually return a value or a null/equivalent in case of inner exception.
    My intent is to have the client of my API not to crash on an input error, just have an error logged, and return a null value. Anyway, he'll have to check if the value is null, and the he can decide what to do with it.
    Is my try-catch inside all functions making my API slow? Am I abusing it ?

    • @dagoberttrump9290
      @dagoberttrump9290 ปีที่แล้ว +2

      Yes. It's double slow as you throw an exception AND the user has to branch on your null value. Just let the exception pass up the call stack to be handled by the user.

    • @az-kalaak6215
      @az-kalaak6215 ปีที่แล้ว

      From what I understand, you could (except if you have a C-style api implemented in c++) let the exception reach the developer part as long as it's documented, and consistant with what you already have. Since exceptions are supposed to mark an error state (it's in their name, exceptional) the slow-down part should not really be an issue (they should not happen a lot)
      ofc, exceptions your side (not related to the user) must not pass or pass as internal_exceptions (since the user cannot fix them)
      If you fear the inputs errors will be many, maybe you could expose a function throwing them all, and a function ignoring them? with defined behaviour in a non-critical piece of code, that could be a solution
      If i'm correct, try catch are supposed to be no-op except when having a throw

  • @whydoineedausername1386
    @whydoineedausername1386 ปีที่แล้ว +1

    Isn't casting -42 to WidgetType UB?
    The valid range for an enum is the full range of ints using as many bits as required to represent the largest member of the enum. With two members you only need one bit => 42 is out of range.
    With three members you have four valid values, so there are more valid values than listed most of the time, but it's not the closest integral type

    • @scarletlettersproductions4393
      @scarletlettersproductions4393 ปีที่แล้ว +4

      That's not the case for enum classes, they can take the full range of values of the underlying integral type (which when unspecified is int).
      The better way to use an enum class and limit the number of valid values here would be to specify the underlying type as bool.

  • @marcbotnope1728
    @marcbotnope1728 ปีที่แล้ว

    Off to remove all std::optional

  • @kuhluhOG
    @kuhluhOG 3 หลายเดือนก่อน

    Btw, technically errno is a threadlocal global these days, but please, don't use it nonetheless. And if you need to, let the first thing you do be "const int error = errno;". People way to often accidentally call a function which changes errno again while trying to handle an error.

  • @fcf8269
    @fcf8269 9 หลายเดือนก่อน

    These days you have hard time to find people that are good communicators on the subject of C++ and at the same time behaving like they are in a professional setup and not on a late night show.
    You can be educative and funny at the same time, without end up doing "comedy" and sarcastic comments. But once you make this a transcript and clean it up from all the nonsense, there is a lot of good material here.

  • @NoNameNoShame22
    @NoNameNoShame22 ปีที่แล้ว +1

    at this point make us all just write template metaprogramming in c++ and remove function bodies, who needs it

  • @ayubyun
    @ayubyun ปีที่แล้ว +1

    hello c++

  • @sawyerwest3990
    @sawyerwest3990 ปีที่แล้ว

    @15:53 is there a way to overwrite the defaults in C++ once per file?

  • @PixelPulse168
    @PixelPulse168 ปีที่แล้ว +2

    ah, opengl has get_last_error. opengl is so old.

    • @MichaelPohoreski
      @MichaelPohoreski ปีที่แล้ว +5

      That's because OpenGL came from IrisGL. Both were written in C before C++ and exceptions even existed.

    • @BGFutureBG
      @BGFutureBG ปีที่แล้ว

      WINAPI as well

  • @neuzhong
    @neuzhong ปีที่แล้ว +1

    C++ become more messy

  • @paulselormey7862
    @paulselormey7862 ปีที่แล้ว +1

    Back to Basics?

  • @rationalcoder
    @rationalcoder ปีที่แล้ว +3

    This contains a few nuggets of good advice, but focuses too much on trivialities and a few falsehoods, like using exceptions in the first place. Yes the defaults of C++ are bad, and the language itself has many issues, but often hacking in a solution in C++ is just not worth the effort vs time spent debugging/testing. Simpler APIs with fewer concepts, but with well known usability issues, are often better than complicated APIs with extra concepts for safety. This is because the ways in which simple APIs can be used correctly and incorrectly are well known, whereas the ways in which an API with bespoke concepts can be used correctly and incorrectly is unique to every library that decides to go this route. Sometimes the answer is just that C++ sucks. Fix the language, put up with it, or work with a different one. It's for this reason that many people including myself simply don't use third party C++ libraries if we can avoid it. I always look for the C version of a library.

  • @vincei4252
    @vincei4252 ปีที่แล้ว +9

    in main() {
    auto l = [] [[nodiscard]] () -> int { return 42; };
    }
    C++ makes me want to cry trying to compete with APL by any chance ?
    And people back in the day used to describe plain old C as looking like modem line noise.

    • @ruadeil_zabelin
      @ruadeil_zabelin ปีที่แล้ว +1

      it's not great but you get used to it. I have no problems reading this anymore. It's the downside of an old language that needs to keep full backwards compatibility.

  • @fareloz
    @fareloz ปีที่แล้ว

    First 5 mins I was thinking this guy needs less alpha-male energy on stage and more informatin to present. But in the end it wasn't that bad, I noted down few intresting things. 3 out of 5 talk.

  • @vipham9355
    @vipham9355 6 หลายเดือนก่อน

    why he can't stand at one spot, just feel dizzy by watching his talk!

  • @curtmcd
    @curtmcd ปีที่แล้ว +6

    I don't know. I've been writing fopen() calls since the 1980s and have never come close to ignoring the result, swapping the arguments, passing in NULL, or accidentally ignoring errors. That FilePtr abomination seems much more prone to issues. It obscures what's going on with long messy code incorporating a dozen implicit complex machinations with way more obscure failure modes.

    • @RigelOrionBeta
      @RigelOrionBeta ปีที่แล้ว +1

      I think the point is for things you don't understand. Of course if you know an API, you'll know what not to do. But if you want to use an API and don't necessarily know every little thing about it, then doing things this way will essentially force you to.

    • @GaryHutchins
      @GaryHutchins ปีที่แล้ว +3

      Remembering that you are not every programmer is always a good idea :)

    • @cdamerius2895
      @cdamerius2895 ปีที่แล้ว

      I agree. To me the FilePtr way of implementing seems to go into the wrong direction. I think it is even worse than the original fopen. The only reasonable thing in the FilePtr implementation to me seems to be the [[nodiscard]], although i never ignored fopen's result, so even that does not seem particularly beneficial.
      First, I don't like that the FilePtr implementation is taking an std::filesystem::path&. Everyone uses a different way of handling strings, maybe zero-terminated, ptr+size or beginptr+endptr. Maybe you get your filepath out of some library. Now everytime you want to call fopen, you have to artificially create an std::filesystem::path object just to call fopen (I think this introduces a copy, doesn't it?). An implementation should settle for the lowest common denominator, which is probably ptr+size or beginptr+endptr. This way, whatever the format of your path string is, you have a way to call this function without creating overhead.
      Second, I don't like the way the fclose() call is obfuscated. This was obviously because we want RAII, to cleanup the resource automatically for us. Okay, we often forget to do that, are we? I am not particularly a fan of this. But then again, there are cases where you want the file object to outlive the current scope. With the FilePtr implementation, you have to know that it automatically fcloses, and need a way (probably a move or something) to transfer the file object to another scope. That seems convoluted. In the original fopen() implementation, that is not necessary. However, i also have to concede that many people are probably used to this kind of way of doing things.
      Third, I would really like the mode string to be something like a bitwise OR of mode flags. Jason mentioned that these are somewhat complicated. But i mean, we have r, w, a, b, +, maybe a few more if I am not mistaken? That does not seem impossible to do with a bitfield. I would generally go for this kind of approach.
      Fourth, i would probably not want the function to return a FILE*, but instead let the user provide the location of the FILE, and fopen() fills that in. Now whether you can do that probably depends on the way that the file control actually deals with FILE's, so that may not be possible (i am unfamiliar with the file controls internals). But at least, this way the allocation would lie on the caller's side. For example, I could now store the FILE object on the stack, which i can't do with the current fopen() implementation.
      Regarding error handling, it really depends on how complex the error handling needs to be. But here I would probably either return an error code, or provide another parameter where fopen can place the error details into. I generally agree that having something like get_last_error() or errno is a bad idea.

    • @Knirin
      @Knirin 10 หลายเดือนก่อน

      @@cdamerius2895Yeah, there are a lot cases where detecting that it failed is the most important thing because you can’t fix the failure in your code. Giving the user/log a good error so they can fix the problem is then second.

  • @kaiszuttor
    @kaiszuttor ปีที่แล้ว

    Quite a dry humor 😅

  • @zxuiji
    @zxuiji ปีที่แล้ว +1

    23:30, I don't c why don't just make an accompanying function called kill_widget or something, then even if the return type is unclear for make_widget (because like f**k am I adding unnecessary garbage to a simple "widget* make_widget( ... );" statement - by the way, bad way to make any widget, always dedicated creation functions like make_txtbox or make_dlgbox) it is always clear how to cleanup the pointer later
    26:37, again a kill_widget() function would clear up the headaches real fast

    • @XeroKimorimon
      @XeroKimorimon ปีที่แล้ว +15

      Same reason why C++ avoids directly calling new + delete. It's faulty, and from skimming the code, it's hard to tell who _should_ be responsible for deleting a pointer. Instead of kill_widget, std::unique_ptr directly says "I am the owner of this pointer, I have responsibility to delete this widget" as std::unique_ptr will delete the pointer when it falls out of scope

    • @zxuiji
      @zxuiji ปีที่แล้ว

      @@XeroKimorimon how is kill_widget obtuse??? you pass it the pointer and clear your own, whether it was static, dynamic or whatever it's the library's job to distinguish that on it's OWN pointers, the library is ALWAYS responsible for the details of a pointer it hands out, it only needs to be told when to cleanup. There is nothing to confuse, additionally my method is C compatible whereas anything involving new,delete,unique_pointer etc is C++ only, a library should target maximum exposure and starting with C is the sensible choice there, not C++, sure it can use whatever it wants under the hood and provide extensions to it's core API according to language but the goal was to achieve max exposure while being simple to use, telling the developer "just pass it to this/these function/s to cleanup" is ALL it should do at the core API

    • @sledgex9
      @sledgex9 ปีที่แล้ว +16

      @@zxuiji Your kill_widget is basically the equivalent of delete. Like all the C libs (eg gtk) having a my_object_free() function for **each** object. Then the caller needs to manually track all exit/return points to clean up (aka call kill_widget). It breaks RAII. The only way to make it work is the caller to wrap the returned raw pointer into a unique_ptr with a custom deleter which uses kill_widget. You've basically thrown away all modern C++ and created a horrible API to use, which is also easy to use wrong ( = the caller forgets to call kill_switch at all return points). I think you totally missed the point of the presentation.

    • @TheJoKeR7
      @TheJoKeR7 ปีที่แล้ว +17

      @@zxuiji so you are basically building a C library/API and not a C++

    • @XeroKimorimon
      @XeroKimorimon ปีที่แล้ว +1

      @@zxuiji I never said it was obtuse, I said it was faulty. Like if you had....
      Widget* a = make_widget();
      And then somewhere else in your code you got
      Widget* b = a;
      Who should call kill_widget()? a or b? Replace the return value with a std::unique_ptr and redo the example
      std::unique_ptr a = make_widget();
      std::unique_ptr b = a; //oh no compiler error, you can't copy unique ptrs
      In order to make the example above work, b would have to be a normal Widget*, and in C++ land, that means b shouldn't call kill_widget(), and a will automatically do that for us, so we'll never forget to call kill_widget(). It also helps us understand that a's lifetime should be longer than b's lifetime, anytime this is false, we clearly have a bug and either make sure both a and b have equal responsibility for it's lifetime via std::shared_ptr, or change something in code to guarantee that a outlives b