Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I don't write C++ for a living, and only have ~2 years' experience writing it for some hobby projects on weekends.

I've found that the way I'm most productive is by using data-oriented design where I put everything in structs and don't use classes. I still use a lot of modern C++ library features, but it reads more like C.

For example:

    struct BufferPool
    {
        alignas(PAGE_SIZE) char frames[BUF_POOL_NUM_FRAMES][PAGE_SIZE];

        std::atomic_uint32_t free_frames[BUF_POOL_NUM_FRAMES];
        std::atomic_uint32_t pin_count[BUF_POOL_NUM_FRAMES];
        std::atomic_bool     is_dirty[BUF_POOL_NUM_FRAMES];
        std::atomic_uint32_t page_id_to_frame_idx[];
    };

    std::span<const char>
    BufferPool_get_record(BufferPool* pool, RecordID rid, int db_file_fd);
What's nice about having all this stuff thrown into the language is that you're free to pick and choose what you want to adopt from it.

For a long time, I tried to cram everything I wrote in C++ into an OOP pattern with classes, and it just didn't jive with me. Then I stopped doing that and started writing it more like TypeScript/Kotlin/C which I'm more familiar with and it's been a lot more pleasant ever since.

There's been so much great stuff in C++ 17/20/23 and you can just cherry pick all the bits you want to use out of it and ignore the rest.



I mean, if you change the name to "BufferPool::get_record" and you use an implicit this pointer instead of your "pool" argument, you have a method. It seems like you're fighting the paradigm more than you need to.


You're not wrong but for some reason this style is easier for me to work with

I know logically/semantically they're equivalent. The v-table function generated if the method is moved inside the class/struct winds up being identical.

Dunno what it is, some kind of familiarity bias with the way the code is laid out I guess, not coming from OOP languages


> The v-table function generated if the method is moved inside the class/struct winds up being identical.

Unless you add some additional semantics/keywords, it's not going to create a vtable.

> Dunno what it is, some kind of familiarity bias with the way the code is laid out I guess, not coming from OOP languages.

I'm sure that's what it is.


Note: it wouldn’t generate a vtable entry unless the method was declared virtual.


Ahh, what do you call the method table thing for non-virtual classes?


C++ uses name mangling to give names to non-virtual methods. They are no different than a C function that takes a pointer, except they may use a different calling convention.

Even if the method is virtual, C++ compilers make an effort to call methods directly without indirection through a vtable wherever possible.


The interesting thing is that you can't call the "function that takes a pointer". Accessing it as a method is the only thing which works.

A long term ambition for C++ and more specifically for Bjarne has been UFCS, Universal Function Call Syntax, which a few other languages have. But C++ can't do this today.


std::mem_fn will effectively give you the underlying function that takes a pointer. Strictly speaking mem_fn is defined to generate the wrapper function, but in practice the optimizer is just going to strip that away and call the name-mangled function directly.

Example: https://godbolt.org/z/6Y1raxMce


You can just use member function pointers without the wrapper. The syntax for calling them is a bit uglier though.


Yup, but then the call syntax is different so it fails to achieve the "Universal Function Call Syntax" that OP was asking for which std::mem_fn provides.


Whoa, this is really cool, thanks for posting


Yeah, UFCS remains illusive, but tantalizingly close. It's pretty easy to make

``` struct foo { void bar(); }

bar(foo* i) { assert(i != nullptr); i->bar(); } ```

work, but there's enough syntactic complexity in the language that it isn't as easy as it should be.


It's a 1-liner with std::mem_fn


Indeed.


There is no table if no ‘virtual’ is involved.


To be more clear, when no "virtual" method is declared, then no table is generated because none is needed.

In this case, the addresses of all the methods are known at compile time and the compiler knows which to choose when anyone is invoked in the source code, based on the type of the object and on the types of the method arguments.

When there is at least one "virtual" method and you have a pointer to an object, the type of the object cannot be known at compile-time, so the compiler cannot determine which method to invoke.

That is why the compiler needs to create a table with pointers to the virtual methods for each class with such methods, and each object of those classes must include a pointer to the corresponding vtable, so that the correct method to invoke can be determined at run time (from the index of the virtual method).


Only classes with one or more virtual functions have virtual method table. Objects of such classes are called polymorphic objects and they (and only they) have hidden pointer to virtual method table.

https://en.cppreference.com/w/cpp/language/object#Polymorphi...


FWIW, structs and classes are largely identical in C++ , with the exception of public/private defaults.

So using one over the other doesn’t really change anything with regards to data oriented design.


Aye, as opposed to D where structs are value types and classes are reference types

It's just a small annoyance to have to put "public:" at the top of every data-class definition

So I use structs everywhere but you're completely right, no real difference


Yeah I do the same as you since I write a ton of Swift (which does the same delineation as D).

It’s mostly just a mental marker for myself that I want to treat one as a value type.


If I was code reviewing this I would strongly suggest std::array instead of those raw array;) I’m also curious why you have page_id_to_frame_idx[] without a size. If you are using that paradigm to read off the end of the struct then it needs commenting heavily, if it’s a typo then std::array would have caught it.

I do understand that it’s just a snippet of code to show a style, so ignore this pedant!




Consider applying for YC's Winter 2027 batch! Applications are open till November 2.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: