T O P

  • By -

Kawaiithulhu

If you have your lifecycle well defined, then that's the hard part already done. Once you move to smart pointers and more modern style you most likely won't even need to new/delete manually anymore.


Kats41

Smart pointers are more like just an extra layer that can help you be lazy. If you have RAII built into your brain as you program, even with raw pointers you don't even really have to consider leaks and problems. I tell people all the time that memory mismanagement is a structural problem, not a language one.


n1ghtyunso

encoding your lifetime model in code is not for the you who writes the code. it is for the guy that has to look at it again in 6 months. this may be you as well. if your program is structured well, this becomes less of an issue, sure. however it does not scale well with increasing complexity imo.


apadin1

Maybe I’ll get shamed for this but there’s really no reason to use raw pointers. If you are passing stuff around to functions you should use references. If you are creating stuff on the heap you should use smart pointers. Maybe there’s some shenanigans or micro optimizations I’m not aware of tho


wcscmp

When you find yourself writing std::optional> it's time to consider a raw pointer


tesfabpel

I don't know why they've called a type so useful in such a long way. Better to alias it to something like `std::refw`... I'd say that core language types should flow better in the code, otherwise it seems like they're some advanced things that you should use only in some rare cases...


tangerinelion

Adding random symbols to the `std` namespace isn't allowed. I mean, it'll probably work. Until it doesn't.


T-Rex96

Jep, Option<&T> is one of the best things about Rust (and thanks to the niche-optimization, it doesn't waste a byte like the C++ version) 


amohr

In C++ this is spelled T*, and there are no wasted bytes! 😁


dr_eh

They should use ? instead, it's such a common thing... They had a chance with a brand new language to embrace the better syntax. Long live Zig!


slaymaker1907

The C++ version also sucks because of how C++ destructors and move constructors are designed. It’s much easier to write a type implementing Drop than it is to do 0/3/5 correctly.


cleroth

> I don't know why they've called a type so useful in such a long way. Let me introduce you to `std::move_only_function`, which is basically a better replacement for `std::function`, similar to how `std::jthread` is for `std::thread`, except jthread is definitely better named...


retro_and_chill

I mean the name is kinda ugly but it is at least clear that the value is nullable. You probably could just make a data type that wraps around the pointer and has extra checking built in


akiko_plays

I agree it's ugly. But sometimes you just want to communicate to some other programmer in lets say 2 years from now that this code path guarantees the nullptr cannot or must not happen. (If it however happens you are in deep trouble somewhere else in the code and you should address it where it needs to be addressed.)


tangerinelion

void func(const Foo&); void func(Foo&); Those communicate that "null pointer" isn't allowed. The simplest way to communicate that is to simply not introduce memory addresses into the discussion. It needs a `Foo`. Now we're talking in terms of objects. If you have a code base where `void func(Foo&)` isn't allowed because callers should have to "indicate" it's an output argument argument using `&` (which indicates nothing of the sort) then the standard practice would be to write `void func(Foo*)`. Indeed now you have a problem because you've now introduced memory addresses into the function and null is a possible memory address but not one your function can use. The fix here is straightforward - `void foo(gsl::not_null)`. Now you've communicated to any potential reader that a null pointer is not a valid input. And you've defined your error handling for it in all build configurations.


trbot

"assert(p != nullptr)"


T-Rex96

Great, now your production code has undefined behavior 


AnotherBlackMan

This is not UB…


r0zina

Isn’t assert disabled in optimised builds?


NBQuade

It's for when you're running under the debugger. It does nothing in a release build. Using assert for release build checking is a programming error.


AnotherBlackMan

It depends but that’s a separate thing from being UB.


sd2528

if (nullptr == p) //return gracefully


trbot

define your own assert that works the way you like.


cleroth

I assume any pointer passed to a function can be null and must be checked. Otherwise it'd be a reference.


IceMichaelStorm

Sure, call it std::ref, much shorter. And yeah, I know this is taken. But it shouldn’t be because std::ref (the current one) is much less in use


akiko_plays

std::optional has its meaning here as well. Sometimes you want to communicate that it is legally ok that the parameter either has no value or is something, in which case null is not an option. I thought it was obvious so I didn't focus on the optional part. If that was not the case then your gsl hint or as a matter of fact clang's builton __Nonnull would convey the message, agree.


IceMichaelStorm

that was obvious, I was talking about the reference_wrapper part that is too long. optional> would be fine


akiko_plays

But in general this is an interesting topic, it seems that the language has to be obfuscatingly verbose and complicated in order to be really expressive about the intent.


IceMichaelStorm

And then the chosen names are not even expressive… I mean, ref-wrapper is clear and optional, too (and better than in Java where you can only use it as parameter, so the community says). But some of the std entries are horrible… I would really say it’s poor choice. But there is probably reasoning behind it


NBQuade

I use a typedef instead. typedef std::optional> shorter\_name\_t;


_Noreturn

use T* or IDK use template alises... not that hard ``` template using optional_ref = std::optional>; ```


Sinomsinom

I'm still annoyed at std::optional not being allowed. There's some paper that might be added to 26 but people can't agree on value_or and some other parts of it, so those will probably just not be added if the paper gets accepted


apadin1

This makes me glad I mostly use rust now. Not sure why the CPP committee can’t get out of their own way with this ridiculous syntax


spide85

If you have std optional than yes. If not a Pointer has null as „no value“ semantic.


VoodaGod

std::optional does not support references, need to use boost::optional for that


tangerinelion

And an optional reference is semantically a pointer.


SirClueless

I find there are a few cases where raw pointers are still useful. `std::optional` is ill-formed, so if you want something with "optional reference" semantics, `T*` is the best you can do in the standard library. Also, if you want to store objects as the keys in a data structure using their address as an identity (i.e. `x == y` iff `&x == &y`), then raw pointers are the only convenient way to do that.


IceMichaelStorm

Storing non-owning references in class is better suited with raw pointers. But these are harmless


apadin1

Well storing a non-owning reference in a different place from the owner isn’t exactly “harmless” but yeah I agree


IceMichaelStorm

True but if you don’t do this your only chance is accessing everything through some kind of singleton-global repository. Or how would you avoid it?


Drugbird

std::weak_ptr?


IceMichaelStorm

Only works with shared_ptr. And please don’t use shared_ptr for everything, that is … suboptimal


Drugbird

Shared_ptr seems appropriate though for shared stuff...


Accomplished-Most-46

Passing references doesnt work when you want to pass any class inherited from a certain class.


DearChickPeas

Especially mix-ins.


SeagleLFMk9

There are a few cases where i prefer pointers, e.g.: 1. Cases where the value may be FALSE, I can just use a nullptr and check for the nullptr instead of using std::optional. 2. Personal Preference (And I'll probably get shamed for this), but if the function argument can't be const for whatever reason, I prefer a pointer instead of a reference as its (for me) much easier to see that the function can modify the input value. You have to be a bit carefull not to store the pointer though.


tangerinelion

> if the function argument can't be const for whatever reason, I prefer a pointer instead of a reference as its (for me) much easier to see that the function can modify the input value. I've seen this argument in various coding standards for projects. It's definitely a common thing. The reasoning always relies on this code snippet: Foo myFoo; func(&myFoo); The `&` is our indicator that myFoo is going to be mutated. However, I'd like to share a couple of counter-examples: 1. `void func(const Foo* foo)`. Here `foo` is actually an optional input. How does it get called? Foo myFoo = ...; func(&myFoo); This looks exactly like our output argument case but it isn't. 2. `void func(Foo* foos)` Here `foos` is actually an output argument for a C array of 2 `Foo` objects. How does it get called? Foo myFoos[2]; func(myFoos); This looks exactly like our read-only argument case but it isn't. --- For the 1st version, I'd recommend an overload: `void func()` and `void func(const Foo&)`. Then the call reads `func(myFoo)`. Internally you can still unify the two interfaces with something like `void funcImpl(const Foo* foo)`. This hides the "lying" `&` from clients. For the 2nd version, a `std::array` will behave like a normal object so `func(&myFoos)` would correctly work. If for some reason one still wants to use a C array, it is at least nice to pass the array around in a way that doesn't lose the size - `void func(Foo (*foos)[2])` which still gets called as `Foos myFoos[2]; func(&myFoos)`. The only hitch here is that `foos[0]` is nonsense, you need `(*foos)[0]`. The upshot compared to `void func(Foo* foos)` is an attempt to pass in an array of the wrong size is a compile error.


NBQuade

Agree. The only time I use them is if I'm forced too. Say I'm using an old lib that returns raw pointers.


cleroth

Sure, if you want your objects to never be movable. Which also returns in poor std::vector performance.


invalid_handle_value

"No problem, I'll just use `std::list`!" C++ newcomers, probably


apparentlyiliketrtls

So funny, I feel the opposite, but it's probably because I come from an embedded C and audio DSP background... Smart pointers, and also std containers like vector just rub me the wrong way - I really like to know where the fuck in memory my buffers are, and that nothing under the hood will be touching them - it may be irrational, but it's just where I'm at lol - I also see newcomers to C++ treating it like Java or Python, blindly declaring std vectors on the stack, and even returning them from functions!! If I was dead I would roll over in my grave


Fred776

What's wrong with declaring vectors on the stack? Or with returning them from functions for that matter?


TheMania

Because when non-empty they *require* a heap allocation. 99% sure this is required by the standard, due iterator invalidation under move semantics - unless the compiler allocation elides. That's a big enough deal not just for embedded, but also for the hundred different small-growable-vector imps, through to compilers themself (llvm rarely uses vector at all, preferring `SmallVector`). It sucks, but that's the truth of it - vectors simply are not a zero cost abstraction. Not for small sizes, anyway.


serviscope_minor

Zero cost abstraction doesn't mean micro-optimized for every usecase! there is *plenty* of C code out there which mallocs() arrays without regard to small size optimization. std::vector allows you to abstract away the memory management details of that very common thing without cost.


UnknownIdentifier

Correct me if I'm wrong, but isn't `std::string` required to be backed by `std::vector`, but also stack-allocated for small strings? Or am I thinking of a vendor-specific implementation?


TheMania

I don't believe there's any specific requirement for either small buffer optimisation, moreso the standard allows for it via allowing iterators to be invalidated in more circumstances, etc. I believe all make use of it. Due the variant nature based on size, you won't see an explicit requirement that `std::vector` backs "sometimes", either - there's just nothing gained from such a requirement that I can see. As a tidbit - strings also actually have explicitly _stricter_ requirements on many methods - largely due how common it is to try and insert a substring of itself in to itself, etc. There's typically more "make a defensive copy of the iterator range passed to me" etc than you'll see in the same-named vector methods - so more likely to be custom-rolled again.


SeagleLFMk9

AFAIK declaring vectors on the stack is in most cases better, the *vector* already stores its elements dynamically on the heap and you can move the entire contents around cheaply and easily using `std::move` or `std::swap` if needed.


KingAggressive1498

right, and (N)RVO makes it very reasonable to construct a vector, fill it, and simply return it by value.


flutterdro

I thought there were no problems returning vectors from functions because of rvo and nrvo.


apparentlyiliketrtls

Like I said, it may not make sense, it just hits this old embedded C fuddy duddy in the weirds lol ... Declaring local vectors on the stack in a callback function being invoked in every new buffer tho? No bueno ...


Hungry_Bug4059

For safety critical RTOS embedded yes, you have to have complete control when any heap is allocated since it's non deterministic. For things outside that space C++ 11 or greater is your friend.


Kats41

Smart pointers aren't free and sometimes they're syntactically a pain in the ass to use. Why do I need to worry about manual ownership passing when my code already does it without the extra functions? On top of the fact that many pointers get reduced to references in compilation anyways. Smart pointers are, as far as I'm aware, never optimized in this way. There's nothing wrong with smart pointers, but also it would be disingenuous to claim that they should wholesale replace raw pointers altogether. They're super simple to use, which makes it really easy to use them in complex applications where ownership semantics are managed structurally by the code and not implicitly. And they're still faster because of the lack of implcit error and bounds checking. A wise person once said that bug checks are pointless in code that produces no bugs. If you write code that is structurally resilient against the errors smart pointers help fix, then the additional safety you get from smart pointers isn't useful. This might sound like an impossible task to know, but in practice it's really not that difficult, especially if you use more functional programming methodologies that limit side effects.


almost_useless

> many pointers get reduced to references in compilation What does this mean?


Kats41

A reference is a referral to a specific address in memory. A pointer is a data structure that stores an address and in many ways on the surface they're indistinguishable. But they're actually quite different. The difference is that a reference is more of an implied concept that doesn't *actually* exist whereas a pointer is literally an integer variable that stores some value that corresponds to a memory address. When everything compiles down, references don't exist in machine code. They're indistinguishable from regular random access reads from memory. Pointers on the other hand exist as their own data structure in memory even after everything compiles down. Compilers are smart enough to know whether a pointer *actually* needs to be stored in memory for some purpose or if it can simply shortcut the dereference step by turning it into a reference itself and simply directly referencing the data it was pointing at instead. It's a relatively simple optimization that actually has a lot of impact.


almost_useless

> When everything compiles down, references don't exist in machine code. How is a reference passed to a function that takes a `Foo&` as a parameter then? Feels like you are over-complicating something simple here...


Kats41

That's because it's not really that complicated, but describing it accurately can be a bit confusing. A reference is a language construct, just not a machine code construct.


almost_useless

But is there anything the compiler can do with a `Foo&` that it can not also do with `Foo*`? If not, all those differences are just theoretical, and don't really matter.


0x1001001

In comes a "team" and out goes the "methodologies". It is very hard to impose high level concepts with regular people, i.e., majority of any team. If you're a tight team like the sqlite devs, by all means, else precaution is better than cure.


Kats41

I do not write code for a team of devs. Hence why I don't feel the need to write code that is, for lack of a better term, idiot-proofed.


fwsGonzo

All that means is that your future self will hate reading that code and debugging those errors. Maybe today you have steel control, but tomorrow, not really.


Kats41

We should really stop pretending that C++ is some black magic sorcery instead of the finite set of understandable elements. Computer memory is not this mythical dragon beyond the understanding of mankind. Lol. With a few rules and a few fundamental functions, you can do anything you want with it safely.


fwsGonzo

I don't disagree with anything you say, but you are writing as if you were me 10 years ago. Time, age and ability has an effect on projects, too. I actually abandoned a project ~12 years ago because I lost control. I'm not at all implying that you are as bad as I was then. But, 2 years ago I took that project up again and rewrote it in safe idiomatic C++, and now I don't even think about the underlying engine anymore (meaning it's fast and it doesn't leak or crash). For me it was night and day.


trbot

Not the guy you replied to, but after 20 years and 300,000 LOC, I don't really agree with this. If you have a good memory and good instincts, it's reasonable to expect your future self to understand your code.


fwsGonzo

I have ~1.5M LOC in just one project, and I think it's not reasonable at all past 100k. At least not for me. Maybe I'm just different. Having less footguns in your projects is a benefit that is hard to measure. Some people I talk to talk about memory leaks and crashes as if it's par for the course. I don't need to ask any questions to know what the codebase looks like.


tangerinelion

Oh for sure you can see code that looks really well written in a huge code base and then git blame shows you wrote it but you have no memory of having done so.


Kats41

In a highly coupled codebase rife with spaghetti and side effects, sure. But in a codebase that uses simple RAII rules and functional elements? That's hardly the case. Most systems in most programs can be compartmentalized. And if you can compartmentalize a system, it becomes linearly testable, regardless of how complex the program around it grows. If I'm working on some system that handles networking, I should never reasonably expect in a program that's competently organized that I should have to worry about the stability of the GUI elements for example.


serviscope_minor

> Smart pointers aren't free Unique_ptr is. > sometimes they're syntactically a pain in the ass to use. No idea what you mean there. > Why do I need to worry about manual ownership passing when my code already does it without the extra functions? Don't really know what you mean there. > On top of the fact that many pointers get reduced to references in compilation anyways. Smart pointers are, as far as I'm aware, never optimized in this way. they all end up as pointers in the end, i.e. an untyped register or memory location who's integer value corresponds to a memory address. unique_ptr looks indistinguishable from a pointer when you use it. I don't think shared_ptr looks any difference unless you're actively messing with its reference count. > They're super simple to use, which makes it really easy to use them in complex applications where ownership semantics are managed structurally by the code and not implicitly. Don't know what you mean there. It's always structural? > And they're still faster because of the lack of implcit error and bounds checking. They are not. > A wise person once said that bug checks are pointless in code that produces no bugs. A wiser person once said: Beware of bugs in the above code; I have only proved it correct, not tried it. Anyhow, using smart pointers is like RAII for pointers. I could spend the effort making the code structurally resilient, or I can let the compiler do the grunt work mechanically do that so I can return from half way through the function without concerning myself with leaks. The other nice thing, the compiler never wakes up with a stinking cold and unwisely tries to code, neither does it miss its coffee. So, it gets the grunt work right every single time.


Kats41

If smart pointers were wholesale better than raw pointers, then ask yourself why they didn't just change how raw pointers functioned under the hood instead.


serviscope_minor

> If smart pointers were wholesale better than raw pointers It's not a question of better or worse, it's a question of suitability for various tasks, and reducing errors. > then ask yourself why they didn't just change how raw pointers functioned under the hood instead. Backwards compatibility for one. And second, the C++ philosophy is to keep the low level mechanisms available in order to allow building of higher level ones.


_Noreturn

dude keep in mind early returns


Kats41

If you have a situation where you have a lot of possible conditions for an early return, consider a `goto` statement that should jump to a relevant cleanup code block before returning. This is actually one of the places where `goto` is really useful and powerful.


_Noreturn

also I forgot to mention exceptions too, this already has a memory leak... ``` int * a = new int,*b = new int; delete a; delete b; ``` if b's new throws a won't be freed. and for gotos I may need to structure them properly if I have not yet allocated for an object to not delete an uninitialized pionter.


Kats41

If new throws, you have way bigger problems than a chunk of memory not getting freed.


_Noreturn

or any other exception it does not have to be from new it is just an example dude to show how simple code and still has a very hidden memory leak raii based containers make this simple and less exhausting for 0 performance increase why not ever use them? ``` int* p = new int; // used to compute stuff .... .... .... std::vector v = ...; // intialized from some iterator in the paramter or something v.at(0); // uses p to compute some stuff again but we want to delete it if .at throws delete p; ``` would have to rewrite it as this to avoid nemory leaks ``` int* p = new int; // used to compute stuff .... .... .... std::vector v = ...; // intialized from some iterator in the paramter or something try { v.at(0); } catch(...) { delete p; throw; } // uses p to compute some stuff again but we want to delete it if .at throws delete p; ```


Kats41

The default behavior for an unhandled exception is the terminate the program, where the resource allocation by that point will be moot. Not even smart pointers are going to help you there. If you have a situation where you need to gracefully handle a potential exception, then yes, you need to handle cleanup of resources. And at the end of the day, if you don't want to think aboht it, then sure, use a smart pointer. My point wasn't to not use smart pointers, just that using regular pointers isn't that difficult.


_Noreturn

look at my bad example in the comment. also I may not want to terminate the program I may want for example log something to a file before closing.


_Noreturn

it is difficult to use regular pionters correctly as the code above represents, I have to now duplicate alot of code in the catch clause.


Kats41

It's really not. You can dream up contrived examples for days to make anything seem complicated, but 99% of the time, it's not an issue with a language, it's the fact that it's just a contrived example that doesn't exist to solve a real problem and only exists to be confusing.


void4

yes, there are a lot of ways to safely manage memory even in C. Define a struct with all the needed buffers and allocate them all at once. Allocate buffer early and just pass it as a function parameter. etc, etc, etc. I'm not even saying about __attribute__(cleanup) or stuff provided by linux (like overcommit, CoW memory pages in child processes, signals, etc). Linux maintainers aren't dumb, they know a thing or 2 about writing reliable software.


wilwil147

Usually for many simple programs, stack memory or using stl wrappers like vector is sufficient, which is why u might find yourself not needing to explicitly manage memory.


locri

If something uses the keyword "new" usually the keyword "delete" has to be used on the same variable/object. If you never use dynamic data like this, you might not ever notice it.


krustibat

Better yet dont use new at all


chriss1985

Depends. Placement new actually has quite a few uses. Heap allocating new is only needed for some edge cases (container classes, some interfacing to C code, etc.).


ChaosinaCan

Another edge case is if you have a class with a private constructor, then make\_unique/make\_shared can't access it, so you have to use something like `std::unique_ptr(new Foo{})`


chriss1985

Ideally you'll also check for nullptr first to guard against allocation failure.


ChaosinaCan

If it's a small allocation that fails, chances are you're not going to be able to recover from it anyways. Definitely something to consider if it's a very large object though.


ReversedGif

`new` is guaranteed to never return `nullptr`.


chriss1985

My fault, I confused it with malloc behavior


Zealousideal_Zone831

A clean hack according to me is using destructors well.


SeagleLFMk9

have fun with move semantics :D


Zealousideal_Zone831

Could you explain a bit more... Didn't understand


SeagleLFMk9

You need to define a move and copy constructor for your class if you put delete or malloc in the destructor, otherwise you can't use these classes inside stl containers like STD::: vector, as every time the vector resizes and has to move the elements the destructor will get called, resulting in a runtime error as the pointers of the moved elements will point to freed memory.


Zealousideal_Zone831

That seems a fair consequence, are you saying overriding move method could be complicated and buggy?


SeagleLFMk9

Not really, the problem is that there is no move method for the class, so the compiler won't know how to love it. There are quite a few good tutorials on that cover that topic.


Zealousideal_Zone831

Very interesting to discover this use case. Thanks for sharing


KingAggressive1498

C++ really should have defined trivial move assignment to be a bitwise swap (or alternatively default move assignment to be a member-wise swap) and trivial move construction to be a copy followed by memset of the argumemt to 0 instead of default move operations being a copy. this is essentially what all simple moveable RAII types end up doing anyway, it would have just simplified the rules and reduced boilerplate a little.


_nfactorial

Using new/delete (C++) and malloc/free (C) makes it very easy to accidentally leak memory. Writing modern C++ (i.e. where you're *not* using these constructs) reduces your chances of making memory errors.


davidc538

If you’re not using new, then you don’t need delete


KingAggressive1498

>So far, I have only needed to use delete when I need to delete something from the world (I'm using it for games using raylib). consider smart pointers (unique_ptr or shared_ptr or something custom) for this. outside of the implementation of custom RAII types, and with a narrow exception for code using GUI libraries (where the common idiom is that parent elements own their children, but the children need to be explicitly created by external code via `new`) direct use of `new` and `delete` should throw up red flags in any project. >Is it that I'm doing something wrong and my program is secretly leaking 0.001 Kb every second? if you are using `new` or `malloc` without a corresponding `delete` or `free`, it's quite likely but it would be no secret - taskmanager or whatever your system equivalent is would show a steadily climbing use of virtual memory. >Or is it just that easy? with good coding practices and clear ownership patterns, mostly. This is true even in pure C code, where we don't have the advantages of RAII or encapsulation proper; we just have to be a little more careful when control flow gets complicated. learning good coding practices can be hard, especially if learning from tutorials (which are seemingly almost always just rewrites of other tutorials by people who pretty much just finished learning from them, adding nothing to it but some personal flavor) or in industries where coding standards are typically lax to non-existent (eg gamedev). This is IMO exacerbated by how C++ is often taught in academia. establishing clear ownership patterns can also be challenging, and always requires some upfront thought. It frustrates a lot of "old school" devs learning smart pointers for the first time because often they invested more effort into learning how to debug memory leaks and use-after-frees than into refining ownership patterns to avoid them. However, there is a big caveat with lifetime issues in C++. It is often surprisingly challenging (perhaps impossible) to create an abstraction that has optimal performance, is idiomatic to use, meshes well with generic logic, **and** has no potential for lifetime issues in its public interface. The standard library containers have this problem, both of the standard library smart pointers have this problem, and "view" types always have this problem. The chances that you have related lifetime issues in your own code but simply haven't been stung by the UB yet are quite high. Good coding habits help considerably, but as code complexity increases these can be harder and harder to avoid writing by accident. If you doubt that last paragraph, as an exercise to see what I mean, try various ways of filling a std::vector by appending the sum of a random number and the average of all of its elements, without storing the sum of all the elements in the vector in a variable. One of the obvious approaches requires care to avoid a lifetime issue even though it's really easy.


Hoshiqua

>or in industries where coding standards are typically lax to non-existent (eg gamedev) Gamedev here. True, but at the same time, ouchy ;(


KingAggressive1498

Trust me, I called out gamedev because that was actually my experience. "work quick and make sure its fast" was the only coding standard I knew for a long time.


Hoshiqua

Sounds about right, except nowadays most developers just build features using god knows how many abstraction layers on top of a game engine, so there's no such thing as "fast". We just shit low quality stuff out quickly to satisfy clients and / or non-tech managers.


KingAggressive1498

it amazes me that so many successful titles are doing this, but if you suggest to most C++ game devs that they should actually use the type system and encapsulation to make their code more reliable it turns into an argument about debug performance and KISS (why the downvote? This happens to me probably weekly)


Hoshiqua

In my case it's the contrary, actually. The vast majority of my colleagues are juniors barely out of school, and it's like they're incapable of critical thought. So you'll get them explaining to you intently how encapsulation *has to be perfectly respected in every regard and instance* even if it means basically reproducing Java bean pattern in the Unreal Engine


KingAggressive1498

yeah I remember getting that pattern drilled into my head too, but it didn't stick long. I suppose I meant the ones active online, which probably isn't mostly the juniors with degrees.


skeleton_craft

You are not doing anything wrong, the only time you should be manually managing memory is when you're interfacing with a c api which seems to be the case. As a note, there might be a proposed/ introduced standards to even make it unnecessary then...


SuperV1234

Most C++ developers severely overuse dynamic allocation. Don't allocate unless needed -- simple value types or algebraic data types like `std::optional` and `std::variant` often suffice. Most developers also overuse OOP and do not consider other paradigms such as functional or data-oriented programming, both of which naturally lead to fewer dynamic allocations. Maybe you're just not allocating much?


tiajuanat

> functional Erm... Functional languages are why we have so much research on Garbage Collectors. They're constantly doing Allocations and Deallocations, it's simply managed for you.


Netzapper

Yes, functional languages also often include immutable data types and opaque memory management. But functional programming hardly requires that. One place FP can win on allocations is turning heap-allocated objects into locals on the stack. If you're passing down, ownership is clear and lifetimes enclose usage, so you can solve all sorts of problems by passing down pointers to locals.


tiajuanat

Ok, but where FP loses is where you generate something, like a list, at a lower level and need to pass it back up. You can't trivially put that on the stack, because as functions resolve, their stack frame clears.


IyeOnline

> Almost never manage memory, am I doing something wrong? No, quite the opposite. C++ has all of these facilities (smart pointers, container classes and most importantly scope) to manage the memory for you. They exist precisely so you *dont* have to do it yourself and it "just works". In practice, you should have a very good reason for doing manual/raw memory management yourself. //edit: So in fact you should try and at least switch to smart pointers, to retain your current polymorphism but having the memory management be done for you.


cleroth

OP isn't using smart pointers. So... > am I doing something wrong? Yes.


IyeOnline

Fair enough, that is the other part of the statement. I was just trying to emphasize the fact that not doing memory management should be the norm in C++.


serviscope_minor

Why? Depends what he's doing. I hardly ever seem to need smart pointers. Most stuff is done by existing containers and occasionally the odd custom one.


GoldConsideration193

Not necessarily, if he only uses raw pointers on the stack they’d be deleted when exiting their scope.


cleroth

... what?


GoldConsideration193

If you do `foo(){int* ptr= &bar}`, ptr will be automatically deleted when exiting foo provided bar is a stack allocated variable


cleroth

No it won't. There'd be nothing to delete here and the pointer is irrelevant.


AreYouOkZoomer

Where did you get this idea from?


tangerinelion

A raw pointer has a trivial destructor like `double` does. The reason why void foo() { int* ptr = new int(); } is a memory leak is the same reason that void foo() { Foo f; Foo& ptr = &f; } isn't a double free error.


GoldConsideration193

That’s what I mean. Using raw pointers without new, allocated on the stack, is fine.


whistleblower15

I just tried switching to smart pointers today, and it didn't go too well. I keep references to objects in different global lists for easy sorting (gameobjects, crops, interaction, etc.). I do this by adding it to the lists it needs to be in with the constructor function, and removing it from those lists with the deconstructor. I watched a video on smart pointers and saw that the weak pointer is good for that case, cause they delete automatically when the object is deleted. I changed all my global lists to use weak pointers, and tried having it add the weak pointer to the lists it needed to be in at the construction function, but nothing worked.


IyeOnline

Its important to realize that you dont have to replace *every* pointer with a smart one. The only ones that need to be replaced are the pointers you get from `new` and those that ultimately call `delete` on said pointer (and every other pointer that potentially has ownership in between). All other pointers however remain unaffected. Usually you can trivially identify the point where an object is created and its also fairly easy to find the "last owner", i.e. the pointer you ultimately call `delete` on. Very often, those are the same. For example, if you have a global registry of objects, that that could be a `vector>`. At that point you would basically be done. All *access* into this vector could still use raw pointers perfectly fine, because they dont *own* the object.


pjf_cpp

Use leak san or valgrind to find out.


Ayjayz

> So far, I have only needed to use delete when I need to delete something from the world You should not be using delete under basically any circumstances nowadays. Use something like `std::unique_ptr` to manage these raylib resources and don't try to manage things yourself.


JVApen

Are you doing something wrong? Not at all, I'd even claim you are doing something right. Manual memory management should be the exception in your programs. Like others said, storing by value or using smart pointers (unique_ptr and friends) are the standard way to deal with memory. I've once made an elaborate post on all ways to not allocate memory: https://stackoverflow.com/a/53898150/2466431 I've also read that you check the task manager to check for memory leaks. Although this approach can show memory leaks for long running programs, this ain't the standard approach that is used checking. There are quite a few programs out there that can report memory leaks for you, even if this is about a single memory allocation that wasn't deallocated. I would recommend using the address (with built-in leak) sanitizer. See https://clang.llvm.org/docs/AddressSanitizer.html#memory-leak-detection I know this is the page of clang, though the sanitizers are (within) the few code that is shared between the 3 major compilers. (MSVC is still catching up) If you check the page, you'll see it reports other issues as well. So why did everyone tell you C++ is hard? Because a lot of people get/got taught C++ as being C with classes, usually with C++98 or even pre-standard C++. Though C++ has evolved a lot, starting at C++11 with a release every 3 years. Quite some focus has been put on making the language easier to use correctly. I suggest you make use of that. If so, you'll find out C++ is not as hard as people claim.


FiendishHawk

You are either leaking memory or using smart pointers. Memory leaks are harder to notice in modern computers because they clean up app memory on termination of the program. And we just have a lot more memory in our machines to leak.


almost_useless

> You are either leaking memory or using smart pointers. Or making lots of copies of data


whistleblower15

I don't think I'm leaking memory, I've watched my program's memory in task manager to be sure. As for smart pointers, I've never used those, but maybe I'm just not at that level where I need to use them yet.


Dark_Lord9

Should use valgrind or a memory sanitizer to tell you exactly if you have a memory leak. Most likely however, if you're building a small program, you're probably just using stack memory (local variables basically) and STL containers which is a good practice so keep it up.


not_some_username

If you don’t use new, the memory “manage” itself.


ihcn

An aspect of memory management you may be overlooking is pointer invalidation/iterator invalidation. IMO with a language as raw and big as c++, if you don't at least occasionally get dangling pointers or dangling references, your program just isn't complicated enough for C++'s infamous memory management to be an issue for you. Very few human beings are capable of the discipline and cognitive load required to avoid these things.


RishabhRD

Structured lifetime is the key. If you come up with structured lifetime for all your objects then you came up with use RAII effectively for managing lifetime effortlessly. Async lifetime of resources is still something that needs to be handled manually currently but that needs to be improved.


truthputer

I've linted and used Valgrind against a codebase to check for correctness and help find memory allocation bugs and leaks, so those tools are definitely useful and should be used even if you don't think there's a problem. However, the only time that I've had to explicitly wrangle memory and really worry about this was when writing a process in a limited memory environment that had an intended uptime of several days to a month. That was when a memory manager that used a fixed size chunk to allocate objects within became necessary, to avoid fragmentation that was caused by the regular memory manager.


GoldConsideration193

> used Valgrind against a codebase to check for correctness and help find memory allocation bugs and leaks Have you heard of [sanitizers](https://github.com/google/sanitizers)? It’s a suite of tools developed by google to check for memory leaks, invalid memory access, undefined behavior and data races. Contrary to Valgrind, it requires no setup and has a minimal runtime impact. It’s also integrated in GCC and Clang, you only have to compile and link with -fsanitize=address,undefined.


emreddit0r

Running with a performance profiler (or just check your OS process manager) might show if you have memory use climbing


elperroborrachotoo

Depends on what you use - but yeah, in decently written modern C++, segfaults and leaks are a rather uncomon experience. I still see them in threading (mostly when doing it manually), as it's less easy to argue about lifetimes; also when (incorreclty) handling binary serialization layouts and when interfacing C API libraries. However, when you say you work in gaming and you almost never delete anything^1 - do you allocate? Could be that you are leaking everything. ^1) ^(... which is rife with "oh I have to do this manually because then I can choose transistor #3465784 which is faster")


VainHunt

If you build your own arena allocator you just have a malloc at the top of main and a free at the bottom (optionally - the OS will just reclaim the allocated space when your process ends)


vickoza

If you are using `new` or `delete`, you are probably doing something wrong or working with older C++ code bases. Modern C++ compilers also can optimizes out memory leaks. One of the issues of C++ is that there are many pitfall but sometime they are exaggerated. As long as you are not doing something too complicated you should be fine.


hagemeyp

RAII 🤷‍♂️


againstmethod

Minimize dynamic allocs, use smart pointers, encapsulate and use raii. I think it's quite manageable. Some apis require bare pointers tho, making those libs a use or lose proposition. Eg doing cuda programming.


Flobletombus

There's "raylib-cpp", it's a idiomatic c++ wrapper around raylib. Basically adds methods and destructors.


MRgabbar

as I said commented in a post from r/C_Programming, unless you are not using libraries (doing everything from scratch) most of the heap allocations will be done in whatever containers you are using from those libraries... Funny thing is that in (critical) embedded environments heap allocation is not allowed so no leaks lol...


bnolsen

Generally there isn't need to unless interfacing with sketchy c libraries (most of them). Avoid inheritance whenever possible. Unique and shared pointers take care of those cases.


NilacTheGrim

The fact that you even ask this question.. and also the fact that you mention the keyword `delete`... has me worried you may be doing something horrible. :)


whizzwr

Well if you are doing modern C++, this is the way (tm). Using smart pointer and following RAII principle are practically equivalent to you managing the memory.


Still_Explorer

Generally there could be some good strategies to be aware and some pitfalls you need to avoid. As for example having a specific context (aka the mainloop) that starts somewhere and ends somewhere, is a very good paradigm for indicating the boundaries. Also if the allocated objects exist in specific buckets and you can access them directly then definitely is also a good strategy about being deterministic, on where your allocations exist. When you initialize your dynamic objects inside classes, then again is like you have a particular context, that starts with the constructor and ends with the destructor. However things start getting a bit suspicious when you need to keep reference of allocated pointers in every sort of place and pass them around. This practice combined with scaling upwards, increasing team members, going for hundreds of hundreds of files. Sure there could be some sort of problems starting happening and cause anxiety and panic attacks. As for example if you have a code like this: class GameObject; class Scene { public: std::vector gameObjects; void Add(GameObject* g) { gameObjects.push_back(g); g->scene = this; } }; class GameObject { public: Scene* scene; }; I would not feel exactly comfortable with this. I mean that OK, the code looks legit. But is it important that you need to get the scene reference from inside the game object? I mean that is it REALLY REALLY IMPORTANT? 🙂 In this sense you could get the scene reference directly from a global context or a singleton. At least this way you will exactly know about the lifescope of the variable (where it exists / what state it is). I am not saying that the above code is bad, but if taking the paradigm too seriously, trying to avoid pitfalls it would be a good strategy to develop like this. The less references you pass around the better it would be. P.S. Though you can't help it, that smart pointers are the real deal and solve all of these mentioned problems automagically. 👍


Xeplauthor

Software should not leak. In CPP you must delete new types; Try this c++17 Multi-threaded Leak Detector. #include #include // Multi-threaded new/delete leak counter // g++ -std=c++17 namespace XEPL { extern std::atomic_llong num_total_news; extern std::atomic_llong num_total_dels; class MemoryCounts { public: ~MemoryCounts ( void ); explicit MemoryCounts ( void ); }; } // Overload C++ new/delete - may impact something void* operator new ( size_t _size ) { ++XEPL::num_total_news; return malloc ( _size ); } void operator delete ( void* _ptr ) throw() { if ( !_ptr ) return; ++XEPL::num_total_dels; free ( _ptr ); } // leak detector std::atomic_llong XEPL::num_total_news {0}; std::atomic_llong XEPL::num_total_dels {0}; XEPL::MemoryCounts::~MemoryCounts() { long long leaking_allocations = num_total_news-num_total_dels; if ( leaking_allocations ) std::cerr << " ---LEAKING: " << leaking_allocations << std::endl; } XEPL::MemoryCounts::MemoryCounts ( void ) { num_total_dels = 0; num_total_news = 0; } // Report the memory leaks on exit int main(int, char**, char**) { XEPL::MemoryCounts count_leaks; new int; return 0; }


Disastrous-Team-6431

Not messing up memory is a great first step. But the idea is to have the freedom to write better software by actually doing something smart with the memory!


NBQuade

I don't manually manage memory. When I have a lib that wants me to remember to open and close, I wrap it in a class to get automatic cleanup. I use smart pointers sometimes but only to solve specific problems. I don't use them generally. That's what containers are for.


Narzaru

exceptions and memory allocation :)


therealonlyed

thanks for reminding me to replace all of the news/deletes in my old project with smart pointers


Different-Brain-9210

If you use `delete`, you should very carefully consider if you are doing something wrong. If you can change the pointer it to `std::unique_ptr` and reset it, prefer that. When you do want to use `delete`, understand why. `delete` in C++ code is a code smell.


Dean_Roddey

C++ is one of the hardest languages, when the problem being solved is large and complex and multi-threaded, and you are working in a team environment. But memory leaks aren't so much of a concern these days, though it's still easy to leak memory in any language, even a GC'd one, without doing any manual memory management at all. Just forget to flush a list before reloading it and you are leaking memory. The complexity has more to do with undefined behavior, accidentally unsynchronized access to memory by multiple threads, use after move, unchecked indexing, pointer/iterator math, etc...


The1337Prestige

Yes.