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

Sdílet
Vložit
  • čas přidán 2. 12. 2022
  • cppcon.org/
    ---
    Back to Basics: C++ API Design - Jason Turner - CppCon 2022
    github.com/CppCon/CppCon2022
    Let’s face it: writing a C++ API can be a daunting task. You recognize that APIs are a critical aspect of your code, and you’d like to provide your users with a great experience, but how?
    This talk will focus on one key aspect: "Making APIs Hard to Use Wrong." How do we design APIs that help, instead of hurt, our users?
    ---
    Jason Turner
    Jason Turner is a regular speaker at C++ conferences, the creator of the C++ Best Practices book, several C++ related Puzzle Books, “Learning C++ Best Practices” video series from O’Reilly and the cppbestpractices.com online C++ coding standards document. As a contractor, speaker and trainer he has specialized in helping others produce high quality C++ code.
    Jason is also host of the CZcams video series, C++ Weekly.
    __
    Videos Filmed & Edited by Bash Films: www.BashFilms.com
    CZcams Channel Managed by Digital Medium Ltd events.digital-medium.co.uk
    #cppcon #programming #api
  • Věda a technologie

Komentáře • 99

  • @fredhair
    @fredhair Před rokem +67

    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 Před rokem

      Putting the fun back in CPP, for sure!

  • @zahash1045
    @zahash1045 Před rokem +66

    Video starts at 3:46

  • @TheClonerx
    @TheClonerx Před rokem +95

    Poor camera guy

    • @joestevenson5568
      @joestevenson5568 Před měsícem +1

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

  • @RayaneCTX
    @RayaneCTX Před rokem +36

    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.

  • @cppevents
    @cppevents Před rokem +9

    Always love hearing Jason's talks. 👍

  • @bryancoxwell5827
    @bryancoxwell5827 Před 9 měsíci +1

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

  • @jplflyer
    @jplflyer Před rokem

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

  • @_Omni
    @_Omni Před rokem +16

    Jason is the best 👏

  • @seancpp
    @seancpp Před rokem +1

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

  • @alidanish6303
    @alidanish6303 Před rokem +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).

  • @dyazmovies
    @dyazmovies Před rokem +2

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

  • @itorrestp
    @itorrestp Před 6 měsíci +1

    Nice to see a conference with people interaction.

  • @MichaelLauerDr
    @MichaelLauerDr Před 20 dny

    Excellent talk. I learned something today.

  • @Dth091
    @Dth091 Před rokem +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 Před měsícem

      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)

  • @prateekpatil4845
    @prateekpatil4845 Před rokem +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"

  • @robertjmccabe
    @robertjmccabe Před 11 měsíci +2

    I like the style of this guy's talks.

  • @MsHofmannsJut
    @MsHofmannsJut Před rokem +14

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

    • @BGFutureBG
      @BGFutureBG Před rokem +6

      exactly, leads to all this keyword mayhem on declarations

  • @ArtoPekkanen
    @ArtoPekkanen Před měsícem

    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.

  • @multiHappyHacker
    @multiHappyHacker Před rokem +2

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

  • @artemp.2122
    @artemp.2122 Před rokem

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

  • @AhmedSam
    @AhmedSam Před rokem +1

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

  • @rainbowkennyHSJ
    @rainbowkennyHSJ Před rokem

    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?

  • @haxpor
    @haxpor Před rokem +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!

  • @intvnut
    @intvnut Před rokem

    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.

  • @KX36
    @KX36 Před rokem

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

  • @toast_on_toast1270
    @toast_on_toast1270 Před 13 dny

    If you return a std::expected/std::variant for error handling, does it break return value optimisation?

  • @zxuiji
    @zxuiji Před rokem

    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 Před rokem +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.

  • @johnmcleodvii
    @johnmcleodvii Před rokem +8

    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.

  • @lucasrangit
    @lucasrangit Před rokem +1

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

    • @hpesoj00
      @hpesoj00 Před 2 měsíci

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

  • @JosefdeJoanelli
    @JosefdeJoanelli Před rokem +7

    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 Před rokem +2

      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.

  • @artemp.2122
    @artemp.2122 Před rokem +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 Před 7 měsíci

      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.

  • @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 Před rokem +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 Před rokem

      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

  • @ZarviroffSerge
    @ZarviroffSerge Před 8 měsíci +1

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

  • @jaybee9054
    @jaybee9054 Před rokem

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

  • @felixbors7546
    @felixbors7546 Před měsícem +1

    Moving up and down does little for the online people.

  • @stephenjames2951
    @stephenjames2951 Před rokem +1

    Dynamic and interesting speaker.

  • @smellytaint
    @smellytaint Před 6 měsíci

    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????

  • @potter2806
    @potter2806 Před rokem

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

    • @meanmole3212
      @meanmole3212 Před 10 měsíci

      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.

  • @whydoineedausername1386
    @whydoineedausername1386 Před rokem +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 Před rokem +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 Před rokem

    Off to remove all std::optional

  • @sawyerwest3990
    @sawyerwest3990 Před rokem

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

  • @BGFutureBG
    @BGFutureBG Před rokem +1

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

    • @CHR73TANGO
      @CHR73TANGO Před rokem +1

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

  • @zxuiji
    @zxuiji Před rokem +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

  • @ayubyun
    @ayubyun Před 11 měsíci +1

    hello c++

  • @NoNameNoShame22
    @NoNameNoShame22 Před rokem +1

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

  • @PixelPulse168
    @PixelPulse168 Před rokem +2

    ah, opengl has get_last_error. opengl is so old.

    • @MichaelPohoreski
      @MichaelPohoreski Před rokem +5

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

    • @BGFutureBG
      @BGFutureBG Před rokem

      WINAPI as well

  • @fcf8269
    @fcf8269 Před 3 měsíci

    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.

  • @vincei4252
    @vincei4252 Před rokem +8

    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 Před rokem +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.

  • @paulselormey7862
    @paulselormey7862 Před rokem +1

    Back to Basics?

  • @neuzhong
    @neuzhong Před rokem +1

    C++ become more messy

  • @kierengracie6883
    @kierengracie6883 Před rokem +9

    So many gotchas, so many guidelines... good for selling books but not for production code? This highlights everything that is wrong with C++

    • @kierengracie6883
      @kierengracie6883 Před rokem +1

      * not good for production code because you can't hire anyone and trust them to write correct code unless they have read all the books or already an expert... how is that supposed to work?

    • @kierengracie6883
      @kierengracie6883 Před rokem +2

      In other words: C++ - it's like trying to make an octopus by nailing 4 legs onto a dog

  • @fareloz
    @fareloz Před rokem

    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 Před 18 dny

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

  • @rationalcoder
    @rationalcoder Před rokem +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.

  • @curtmcd
    @curtmcd Před rokem +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 Před rokem +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 Před rokem +3

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

    • @cdamerius2895
      @cdamerius2895 Před 7 měsíci

      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 Před 3 měsíci

      @@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 Před rokem

    Quite a dry humor 😅

  • @zxuiji
    @zxuiji Před rokem +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 Před rokem +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 Před rokem

      @@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 Před rokem +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 Před rokem +17

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

    • @XeroKimorimon
      @XeroKimorimon Před rokem +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