In my opinion information hiding and binary compatibility is typically the only decent reason to only allow heap allocation of structures besides variable-length structs (which would always require it, or at least be a little bit awkward to use otherwise if the client had to allocate memory on the stack in a VLA fashion to allocate the VLS). Even large structs are often cheaper to return by value if that means the software working much more with the hot memory on the stack. And even if they weren't cheaper to return by value on creation, one could simply do this:
In my opinion information hiding and binary compatibility is typically the only decent reason to only allow heap allocation of structures besides variable-length structs (which would always require it). Even large structs are often cheaper to return by value if that means the software working much more with the hot memory on the stack. And even if they weren't cheaper to return by value on creation, one could simply do this:
In my opinion information hiding and binary compatibility is typically the only decent reason to only allow heap allocation of structures besides variable-length structs (which would always require it, or at least be a little bit awkward to use otherwise if the client had to allocate memory on the stack in a VLA fashion to allocate the VLS). Even large structs are often cheaper to return by value if that means the software working much more with the hot memory on the stack. And even if they weren't cheaper to return by value on creation, one could simply do this:
It's not such a valid reason at least from a performance standpoint to make fopen return a pointer since the only reason it'd return NULL is on failure to open a file. That would be optimizing an exceptional scenario in exchange for slowing down all common-case execution paths. There might be a valid productivity reason in some cases to make designs more straightforward to make them return pointers to allow NULL to be returned on some post-condition.
For file operations, the overhead is relatively quite trivial compared to the file operations themselves, and the manual need to fclose cannot be avoided anyway. So it's not like we can save the client the hassle of freeing (closing) the resource by exposing the definition of FILE and returning it by value in fopen or expect much of a performance boost given the relative cost of the file operations themselves to avoid a heap allocation.
... to initialize Foo from the stack without the possibility of a superfluous copy. Or the client even has the freedom to allocate Foo on the heap if they want to for some reason.
For file operations, the overhead is relatively quite trivial compared to the file operations themselves, and the manual need to fclose cannot be avoided anyway. So it's not like we can save the client the hassle of freeing the resource by exposing the definition of FILE and returning it by value in fopen or expect much of a performance boost given the relative cost of the file operations themselves to avoid a heap allocation.
... to initialize Foo from the stack without the possibility of a superfluous copy.
It's not such a valid reason at least from a performance standpoint to make fopen return a pointer since the only reason it'd return NULL is on failure to open a file. That would be optimizing an exceptional scenario in exchange for slowing down all common-case execution paths. There might be a valid productivity reason in some cases to make designs more straightforward to make them return pointers to allow NULL to be returned on some post-condition.
For file operations, the overhead is relatively quite trivial compared to the file operations themselves, and the manual need to fclose cannot be avoided anyway. So it's not like we can save the client the hassle of freeing (closing) the resource by exposing the definition of FILE and returning it by value in fopen or expect much of a performance boost given the relative cost of the file operations themselves to avoid a heap allocation.
... to initialize Foo from the stack without the possibility of a superfluous copy. Or the client even has the freedom to allocate Foo on the heap if they want to for some reason.
As an example, I can actually run some ancient programs built during the Windows 95 era today (not always perfectly, but surprisingly many still work). Chances are that some of the code for those ancient binaries used opaque pointers to structures whose size and contents have changed from the time they were builtWindows 95 era. Yet the programs continue to work in new versions of windows since they weren't exposed to the contents of those structures. When working on a library where binary compatibility is important, what the client isn't exposed to is generally allowed to change without breaking backwards compatibility.
struct Foo { //* priv_* indicates that you shouldn't tamper with these fields! */ int priv_internal_field; int priv_other_one; }; struct Foo foo_create(void); void foo_destroy(struct Foo* foo); void foo_something(struct Foo* foo); struct Foo { //* priv_* indicates that you shouldn't tamper with these fields! */ int priv_internal_field; int priv_other_one; //* reserved for possible future uses (emergency backup plan). // currently just set to null. */ void* priv_reserved; }; int foo_create(struct Foo* foo); ... /* In the client code: */ struct Foo foo; if (foo_create(&foo)) { ...foo_something(&foo); foo_destroy(&foo); } ... to initialize Foo from the stack without the possibility of a superfluous copy.
As an example, I can actually run some ancient programs built during the Windows 95 era today (not always perfectly, but surprisingly many still work). Chances are that some of the code for those ancient binaries used pointers to structures whose size and contents have changed from the time they were built. Yet the programs continue to work in new versions of windows since they weren't exposed to the contents of those structures. When working on a library where binary compatibility is important, what the client isn't exposed to is generally allowed to change without breaking backwards compatibility.
struct Foo { // priv_* indicates that you shouldn't tamper with these fields! int priv_internal_field; int priv_other_one; }; struct Foo foo_create(void); void foo_destroy(struct Foo* foo); void foo_something(struct Foo* foo); struct Foo { // priv_* indicates that you shouldn't tamper with these fields! int priv_internal_field; int priv_other_one; // reserved for possible future uses (emergency backup plan). // currently just set to null. void* priv_reserved; }; int foo_create(struct Foo* foo); ... /* In the client code: */ struct Foo foo; if (foo_create(&foo)) { ... foo_destroy(&foo); } ... to initialize Foo without the possibility of a superfluous copy.
As an example, I can actually run some ancient programs built during the Windows 95 era today (not always perfectly, but surprisingly many still work). Chances are that some of the code for those ancient binaries used opaque pointers to structures whose size and contents have changed from the Windows 95 era. Yet the programs continue to work in new versions of windows since they weren't exposed to the contents of those structures. When working on a library where binary compatibility is important, what the client isn't exposed to is generally allowed to change without breaking backwards compatibility.
struct Foo { /* priv_* indicates that you shouldn't tamper with these fields! */ int priv_internal_field; int priv_other_one; }; struct Foo foo_create(void); void foo_destroy(struct Foo* foo); void foo_something(struct Foo* foo); struct Foo { /* priv_* indicates that you shouldn't tamper with these fields! */ int priv_internal_field; int priv_other_one; /* reserved for possible future uses (emergency backup plan). currently just set to null. */ void* priv_reserved; }; int foo_create(struct Foo* foo); ... /* In the client code: */ struct Foo foo; if (foo_create(&foo)) { foo_something(&foo); foo_destroy(&foo); } ... to initialize Foo from the stack without the possibility of a superfluous copy.