Back to Basics: C++ API Design - Jason Turner - CppCon 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 KZread video series, C++ Weekly.
__
Videos Filmed & Edited by Bash Films: www.BashFilms.com
KZread Channel Managed by Digital Medium Ltd events.digital-medium.co.uk
#cppcon #programming #api

Пікірлер: 99

  • @fredhair
    @fredhair Жыл бұрын

    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!

  • @TheClonerx
    @TheClonerx Жыл бұрын

    Poor camera guy

  • @joestevenson5568

    @joestevenson5568

    Ай бұрын

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

  • @zahash1045
    @zahash1045 Жыл бұрын

    Video starts at 3:46

  • @yangwei6761

    @yangwei6761

    Жыл бұрын

    ty, swordsman.

  • @RayaneCTX
    @RayaneCTX Жыл бұрын

    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 Жыл бұрын

    Always love hearing Jason's talks. 👍

  • @bryancoxwell5827
    @bryancoxwell582710 ай бұрын

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

  • @MichaelLauerDr
    @MichaelLauerDrАй бұрын

    Excellent talk. I learned something today.

  • @_Omni
    @_Omni Жыл бұрын

    Jason is the best 👏

  • @jplflyer
    @jplflyer Жыл бұрын

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

  • @seancpp
    @seancpp Жыл бұрын

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

  • @alidanish6303
    @alidanish6303 Жыл бұрын

    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).

  • @itorrestp
    @itorrestp7 ай бұрын

    Nice to see a conference with people interaction.

  • @dyazmovies
    @dyazmovies Жыл бұрын

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

  • @Dth091
    @Dth091 Жыл бұрын

    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

    Ай бұрын

    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)

  • @MsHofmannsJut
    @MsHofmannsJut Жыл бұрын

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

  • @BGFutureBG

    @BGFutureBG

    Жыл бұрын

    exactly, leads to all this keyword mayhem on declarations

  • @prateekpatil4845
    @prateekpatil4845 Жыл бұрын

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

  • @multiHappyHacker
    @multiHappyHacker Жыл бұрын

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

  • @robertjmccabe
    @robertjmccabe Жыл бұрын

    I like the style of this guy's talks.

  • @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

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

  • @johnmcleodvii
    @johnmcleodvii Жыл бұрын

    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.

  • @JosefdeJoanelli
    @JosefdeJoanelli Жыл бұрын

    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

    Жыл бұрын

    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.

  • @toast_on_toast1270
    @toast_on_toast1270Ай бұрын

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

  • @intvnut
    @intvnut Жыл бұрын

    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.

  • @AhmedSam
    @AhmedSam Жыл бұрын

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

  • @lucasrangit
    @lucasrangit Жыл бұрын

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

  • @hpesoj00

    @hpesoj00

    3 ай бұрын

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

  • @artemp.2122
    @artemp.2122 Жыл бұрын

    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

    8 ай бұрын

    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.

  • @ArtoPekkanen
    @ArtoPekkanen2 ай бұрын

    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.

  • @haxpor
    @haxpor Жыл бұрын

    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!

  • @KX36
    @KX36 Жыл бұрын

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

  • @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

    Жыл бұрын

    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

  • @felixbors7546
    @felixbors75462 ай бұрын

    Moving up and down does little for the online people.

  • @ZarviroffSerge
    @ZarviroffSerge8 ай бұрын

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

  • @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

    Жыл бұрын

    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.

  • @smellytaint
    @smellytaint7 ай бұрын

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

  • @jaybee9054
    @jaybee9054 Жыл бұрын

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

  • @stephenjames2951
    @stephenjames2951 Жыл бұрын

    Dynamic and interesting speaker.

  • @potter2806
    @potter2806 Жыл бұрын

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

  • @meanmole3212

    @meanmole3212

    11 ай бұрын

    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 Жыл бұрын

    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

    Жыл бұрын

    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.

  • @BGFutureBG
    @BGFutureBG Жыл бұрын

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

  • @CHR73TANGO

    @CHR73TANGO

    Жыл бұрын

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

  • @sawyerwest3990
    @sawyerwest3990 Жыл бұрын

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

  • @zxuiji
    @zxuiji Жыл бұрын

    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

  • @marcbotnope1728
    @marcbotnope1728 Жыл бұрын

    Off to remove all std::optional

  • @ayubyun
    @ayubyun11 ай бұрын

    hello c++

  • @NoNameNoShame22
    @NoNameNoShame22 Жыл бұрын

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

  • @PixelPulse168
    @PixelPulse168 Жыл бұрын

    ah, opengl has get_last_error. opengl is so old.

  • @MichaelPohoreski

    @MichaelPohoreski

    Жыл бұрын

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

  • @BGFutureBG

    @BGFutureBG

    Жыл бұрын

    WINAPI as well

  • @vincei4252
    @vincei4252 Жыл бұрын

    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

    Жыл бұрын

    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 Жыл бұрын

    Back to Basics?

  • @neuzhong
    @neuzhong Жыл бұрын

    C++ become more messy

  • @fcf8269
    @fcf82694 ай бұрын

    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.

  • @kierengracie6883
    @kierengracie6883 Жыл бұрын

    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

    Жыл бұрын

    * 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

    Жыл бұрын

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

  • @vipham9355
    @vipham9355Ай бұрын

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

  • @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.

  • @curtmcd
    @curtmcd Жыл бұрын

    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

    Жыл бұрын

    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

    Жыл бұрын

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

  • @cdamerius2895

    @cdamerius2895

    8 ай бұрын

    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

    4 ай бұрын

    @@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 😅

  • @rationalcoder
    @rationalcoder Жыл бұрын

    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.

  • @zxuiji
    @zxuiji Жыл бұрын

    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

    Жыл бұрын

    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

    Жыл бұрын

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

    Жыл бұрын

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

  • @XeroKimorimon

    @XeroKimorimon

    Жыл бұрын

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

Келесі