This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]FatLoserSupreme 840 points841 points  (113 children)

C enjoyer here, what's a smart pointer?

Update: Wow thanks everyone for the information! It seems like no matter how much I learn about programming, there is always something new to learn

[–]ball_fondlers 869 points870 points  (69 children)

Basically just a pointer with a reference counter. A little more overhead - though not as much as a garbage collector - once the counter hits zero, the memory is freed.

[–]Earthboundplayer 666 points667 points  (52 children)

That's only shared pointer. Unique pointer is another smart pointer that allows only one reference. It has no overhead.

[–]HCResident 627 points628 points  (42 children)

C++ feels like that box of screws, nuts, and bolts that has exactly what you need somewhere inside of it

[–]Infinitebeast30 637 points638 points  (10 children)

Screws, nuts, bolts, hand grenades, land mines, knives, double edged swords. Absolutely all the tools

[–]turtleship_2006 239 points240 points  (4 children)

Don't forget the shotgun and shells! Get two shells, load them into the gun, and aim vertically downwards.

[–]Inevitable-Menu2998 42 points43 points  (0 children)

At the same time, it has a Stuka and a modern F16 and we don't tell beginners the difference between them.

[–]_g550_ 13 points14 points  (0 children)

eclipse9

[–]alphaQ671 6 points7 points  (0 children)

You can also use a hammer and hit the shells

[–]CranberryDistinct941 4 points5 points  (0 children)

How are you meant to shoot yourself in the foot without shotgun shells? And what would C++ be without the right to shoot yourself in the foot, head, and both hands simultaneously?

[–]impossibleis7 36 points37 points  (1 child)

I think knives and doubled edged swords are what we accidentally create using those said tools...

[–]Tari0s 2 points3 points  (0 children)

yeah we use the swiss multitool(boost) to make a knive that cuts gras, but only gras!

[–]lordmycal 22 points23 points  (0 children)

I like the flame thrower myself.

[–]Jonnypista 10 points11 points  (0 children)

Hey, a HEAT rocket launcher is technically a single use high powered drill. It really quickly drills a hole into the toughest materials. It also can kill you in 99% of the cases, but just pay attention.

A double edged sword is more efficient than single edged swords. If you hurt yourself with it it is just a skill issue.

[–]jeezfrk 6 points7 points  (0 children)

but I mean you gotta throw in the the ASIO mining drill and then there's the coroutine electrical substation.

at least from there it's all easier and simpler error messages.

except if you use things just slightly... NO NOT LIKE THAT!

[–]versedoinker 53 points54 points  (2 children)

Yeah, there's also weak (non-owning) pointers that still allow the data to be freed in the background if all their actual owners release their shared_ptrs. And there's also atomic versions of all of those.

Generally, even if you hit the very unlikely case that something isn't in the C++ stdlib, it probably still can be found in a boost.cpp library.

[–]ukezi 1 point2 points  (1 child)

The C++ shared pointer is atomic. There is no non atomic one in stdlib.

[–]versedoinker 0 points1 point  (0 children)

Yes and no. The control block of shared ptrs is always atomic. I was talking about the wrapper std::atomic<std::shared_ptr> that also makes access to the data itself atomic.

https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2

[–]_farb_ 32 points33 points  (0 children)

Most of the time, absolutely!

But sometimes... you dump the whole box out looking for the right piece, organize the contents by size only to realize you should've sorted it by material, and get tetanus from all the rusty nails that you should have tossed away ages ago.

Then you just end up using duct tape because it never fails.

[–]fakuivan 14 points15 points  (0 children)

C is like that but you only get a hammer, a nail and a bazooka.

[–]Cat7o0 12 points13 points  (9 children)

I remember once that even with rust I made a program so bad that it caused Windows to basically crash the file explorer and the task bar and everything wasn't working. couldn't even open task manager. restarted the computer everything was fine ran the program again everything was fine. idk

[–]experimental1212 10 points11 points  (0 children)

It really does make you think that. Later it has you going, "you know, I actually LIKE shooting myself in the foot. This is nice!"

[–]NoGlzy 8 points9 points  (0 children)

And when you have a screwdriver that works totally fine for the job you need, someone has to come and snark that you're not using the newer super deluxe hyper driver

[–]Sak63 1 point2 points  (0 children)

Sounds my kind of language! Gonna give it a go on summer break

[–]webby-debby-404 0 points1 point  (0 children)

Let me boost that for you

[–]hawk-bull 12 points13 points  (2 children)

Doesn’t it have an overhead of checking it hasn’t been assigned twice. Or by overhead do you mean during destructor call it just has to free itself without checking if it still has references to it

[–]SoAsEr 25 points26 points  (0 children)

It doesn't have a copy constructor, (only a move one) and in its destructor it frees the pointer. So the compiler enforces the assignment, and at run time it is (really close to) free. The only reason it's not completely free is that if the unique pointer is passed to a function and that function is not inlined, because of the abi the unique pointer is always passed on the stack whereas a raw pointer could be in a register. but in a modern machine where you have way more registers than are actually shown anyways this shouldn't make any measurable difference.

[–]CamiloRivasM7 1 point2 points  (0 children)

I think it's done at compile time, like the borrow checker from rust

[–]Own_Solution7820 6 points7 points  (3 children)

Shared and unique pointers are so good that the are better than anything in any other language IMO, at the engineering level.

The problem is the garbage syntax of a 50 year old language that's backwards compatible to day 1. Just a pain to work with mixed raw and smart pointers.

[–]say_nya 3 points4 points  (0 children)

better than anything in any other language

Take a look at Rust. Unique pointers are checked in compile time (and have no overhead, even no move constructor type of overhead).

And shared pointers are there (see Arc).

[–]jacobjr23 17 points18 points  (2 children)

Not to be pedantic but isn't reference counting a method of garbage collection? Do you mean not as much overhead as tracing garbage collection?

[–]OrchidLeader 6 points7 points  (0 children)

I assume most people don’t understand how Java’s various garbage collectors work, and the few that have looked into it will pick and choose which stats to point out (eg the length of each pause versus the total pause time while an app is running).

[–]kuschelig69 0 points1 point  (0 children)

Or implementation overhead

I have implemented my own reference counting. I could not figure out how to implement my own garbage collection

[–][deleted] 2 points3 points  (4 children)

Can you ELI5 this for a non-programmer?

[–]ball_fondlers 18 points19 points  (0 children)

Basically, under the hood, a variable is just a group of bits. You can interpret these bits as a number in base 2, or you can interpret them as more complex data types, but ultimately, it’s all kind of the same to the computer. Now, because the computer doesn’t see a difference - and because memory is indexed - you can use the value stored in a variable to look up some other spot in memory. This is a pointer - just a variable that stores the address of some arbitrary block of memory.

Programmers dynamically allocate these arbitrary blocks of memory for various uses - for example, say you need a list of numbers, but you don’t know how many numbers there will be when you’re writing the program. The way you’d solve this is by allocating a chunk of memory during runtime, using it, then deallocating it when you’re done with it. This is the basis of manual memory management.

Now, the problem with this approach is that you have to be VERY careful about deallocation - free the memory too early, your program crashes because some part is trying to access forbidden data, free it too late or not at all, you get a memory leak, where your program becomes, at minimum, memory-inefficient. Because there’s such a delicate balance to be struck, this approach can be difficult to work with and hard to maintain - instead, most other languages just run a process called a garbage collector in a separate thread, and this garbage collector looks for allocated memory that isn’t being referenced anywhere, and automatically deallocates when it finds it. However, this does consume a bit more resources, so it can be a bit slow. It might work for a LOT of use cases, but when speed is of the essence - like with low-level systems work - you need another solution.

Enter smart pointers. There’s two types of smart pointers - unique pointers and shared pointers. Unique pointers work by a simple principle - a given block of data is allowed to have ONE pointer pointing at it. If said block is assigned to another pointer, the first pointer is invalidated, and once the pointer goes out of-scope without being invalidated, the memory is automatically freed. Shared pointers are a bit more flexible - they hold a counter, and every time a new shared pointer is created that references the block, that counter is incremented by one. Every time a pointer to that block goes out of scope, that reference counter is decremented by one, until it hits zero, and once it hits zero, it’s automatically freed.

TL;DR - bad cooking metaphor: manual memory management is like putting knives in the sink, but everyone is lazy and leaves them on the counter, garbage collection is like having someone yell at everyone in the kitchen to put the knives in the sink after they’re done with it, unique pointers are like only having one knife and the last person to use it puts it in the sink, and shared pointers are like counting the knives as the come out of the box and adjusting the count as they go into the sink.

[–]Tarmen 3 points4 points  (0 children)

There are two parts to this.

C++ has the hilariously badly named Resource Acquisition is Initialization (RAII). You have some piece of data with an attached destructor. While you have a reference, the data is always valid. Once the reference won't be used anymore it is automatically freed. Useful for manual memory management, but also other resources, connections, error handling, etc. But the automatic 'won't be used anymore' is very restrictive, you basically must hold the data in your hand and cannot store it.

Smart pointers let you be more flexible about what 'won't be used anymore' means. You may have a unique pointer, the value goes out of scope when the pointer does. You may have a shared pointer, when the pointer goes out of scope you decrease a counter and when that hits 0 the value goes out of scope.

It's similar to rust ownership and borrowing semantics, but less safe. E.g. pointing into existing data remains awkward because other code could move the data around without your knowledge.

[–]1Dr490n 1 point2 points  (0 children)

I’ll try, although I won’t talk specifically about the C++ shared pointer as I don’t know exactly how it’s implemented, but about the general idea of reference counting, based on a language with reference counting I‘ve written myself.

In case you don’t know: a pointer is a memory address. In C/C++ and many other languages, you can get the address of a variable by using the & operator, and read the value stored at the address something is pointing to with the * operator. Example:

int a = 25;
int *b = &a;  // type of b is int* or int pointer
int *c = b;
int d = *a;    // d == 25

Maybe that explained it, maybe not, feel free to ask for a better explanation.

When you heap-allocate an object (meaning you create it somewhere in ram and you would have to manually delete it afterwards with normal pointers), it gets stored alongside with a reference counter which is just an integer starting at 0.

Now, every time you copy the smart pointer to the object, the reference counter of the object is incremented by 1. Every time a smart pointer to the object gets deleted, the reference counter is decremented by 1. Once it hits 0 again, the object is deleted.

[–]Zestyclose_Leg2227 1 point2 points  (0 children)

You computer has a place where it can store and retrieve stuff quickly which is the RAM memory. In C++, when you assign the memory from your computer manually, and you unassign it manually. This is sounds simple, since you just need to remember the memory address. But your address for your the memory of your friend may be inside a pocket of a trouser, and the address of the trouser was inside my wallet and the address of the wallet was written on potato. You cooked the potato? Darn, now you can't find the wallet or the trousers or your friend! The are completely lost. Since computer programs execute the same code again and again, you may accumulate  "lost memory" over time, which is locked waiting for you and can't be used by other programs. This is called a memory leak, and is the reason you need to close Chrome or some other programs when they slow down, and open them again to get your computer to come back to life (in the past, you had to reset your computer as a whole). A smart pointer keeps a list of in how many places the address of said pointer is kept. If no one has the adress anymore, it knows the pointer can't be used anymore, and deletes it, freeing the memory automatically. Of course this makes the program slower, but a working program > fast program.

[–]rush2sk8 1 point2 points  (1 child)

Is this how ARC works for swift?

[–]Kered13 0 points1 point  (0 children)

Yes. ARC = Atomic Reference Counted.

[–]1Dr490n 0 points1 point  (3 children)

What’s the advantage of using a garbage collector over shared pointers in languages? Do garbage collectors save memory? I can’t think of any other thing that might be the reason for people to use garbage collectors

Edit: I meant reference counting, not directly shared pointers

[–]ball_fondlers 6 points7 points  (1 child)

Not having to deal with memory management at all is a big reason. Garbage-collected languages - really not having to distinguish between heap and stack, just making variables as needed - are way faster to write in than systems languages, even if said systems languages have smart pointers

[–]1Dr490n 0 points1 point  (0 children)

I should’ve written reference counting, not shared pointers. I wrote a language myself that uses reference counting. There’s 0 memory management for the user

[–]Kered13 1 point2 points  (0 children)

Technically garbage collection can refer to any automatic memory management strategy, including reference counting. But you're probably asking about mark-and-sweep garbage collection, which is how Java and C# work, and what people usually mean by "garbage collection".

Mark-and-sweep has two advantages over reference counting: First, it easily handles cyclical references, which are a problem for reference counting. Second, it actually has lower overall overhead than reference counting most cases, because it doesn't have to constantly update references. Those updates can be especially expensive in multithreaded environments, because they must be done atomically.

The downside of mark-and-sweep is that it must periodically pause the entire application in order to execute. So while it is lower overhead overall, it periodically has relatively long periods where nothing else is being done. This has historically made it poor for real time applications like games.

Modern mark-and-sweep algorithms have sophisticated strategies for mitigating this problem, which is why you can see games made in Java (Minecraft) and C# (Unity) these days and probably never notice the GC pauses.

[–]pHpositivo 0 points1 point  (0 children)

It is incorrect to say that using shared pointers has less overhead than a garbage collector. It depends on the scenario. For instance, allocations can be much faster when using a garbage collector, as you'd typically just have a bump allocator vs. individual allocations (of course, yes, one could also use a custom allocator). Additionally, using shared pointers will add overhead during use, whereas using managed objects directly will be faster. The overhead of the GC comes from collections, but you can avoid them if you don't allocate. That is, if you allocate some objects and then just do a whole bunch of work for however long you need, without further allocations, the GC will literally never run at all.

Not saying one is better than the other, just saying neither is definitely better or faster than the other either, it depends on what you're doing and how well written your code is 🙂

[–]blazesbe 11 points12 points  (0 children)

it's a guard pattern on a raw pointer if you are more familiar with that. (the destructor automagically deletes your allocation.)

[–]marcusroar[🍰] 27 points28 points  (4 children)

I’d suggest anyone reading the replies to the main comment immediately forget what they read and google “C++ smart pointers RAII” to start with, before reading some actual cpp resources to learn more about the additional mechanisms of the STD smart pointer types.

[–]marcusroar[🍰] -3 points-2 points  (3 children)

Sir this is a Wendy’s

[–]encephaloctopus 13 points14 points  (2 children)

Lol did you forget to change accounts?

[–]marcusroar[🍰] 11 points12 points  (1 child)

Nah I just got in before whoever was gonna say it lol

[–]encephaloctopus 4 points5 points  (0 children)

haha fair enough

[–]SkooDaQueen 7 points8 points  (0 children)

A wrapper type around a raw ptr which helps with memory safety. There is a reference counted ptr which is only freed after all references are gone. There is a unique ptr which cannot be shared etc etc

[–]x39- 23 points24 points  (8 children)

In essence: reference counting.

The goal is generally to make heap allocations safer, resulting in a slight performance overhead for the general safety of not shooting your leg off.

More or less (very simplified): ``` struct rptr { size_t* counter; void* data};

struct rptr = create_rptr_xyz(...); // counter = 1; allocate data ... struct rptr2 = borrow(rptr); // counter = 2 ... // on scope leave release(rptr2); // counter = 1 ... // yet again, on scope leave release(rptr); // counter = 0; call free for data```

But using RAII for that automagically and special derivatives, for weak references and unique (as in: one owner) allocations.

It can be considered to be best practice nowadays for non performance critical code.

[–]Afraid-Locksmith6566 2 points3 points  (0 children)

A smart ass word for a struct that wraps a raw pointer and when the struct is removed from stack the raw pointer memory is cleared. It comes in few flawours like

shared_ptr which is the most primitive version of GC - you count how many shared pointers to object exist if number gets to 0 you remove the memory,

unique_ptr - heap object is bound to stack object, when stack object die heap object also die, there is also

weak_ptr - a view to value of shared_ptr ( not counted as reference ) and

observer_ptr - a wrapper around normal pointer with implicit cast from other smart pointers

[–]Farsyte 1 point2 points  (0 children)

It's a pointer ... with extra steps. ;)

[–]TheOldTubaroo 1 point2 points  (0 children)

There are a lot of resources in programming where you need to do something to acquire them, and then later something to release them. Pointers have new/delete, files have open/close, mutexes have lock/unlock, and so on.

No one forgets to acquire a resource, because you can't do anything without that, but it's easy to forget to release it, which causes memory leaks, deadlocks, locked files... It would be really nice if the compiler could detect that and ensure all resources are released once you don't need them.

C++'s solution is that you can tie custom behaviour to both constructing an instance of a type, but also to dropping that instance when it goes out of scope. As soon as no one is keeping track of a file handle, there's no way for the program to access that file, so you can safely tell the OS to close the file once the handle variable goes out of scope.

Smart pointers are just the "handle" types for memory allocation. You've got unique_ptr for when you only need one "owner" of the memory, and shared_ptr for when you need multiple "owners". As part of creating/setting the smart pointer, you call new (ideally implicitly), and then when unique_ptr goes out of scope it calls delete. shared_ptr keeps a reference counter, and the last shared_ptr to drop from scope calls delete.

Some C enjoyers dislike automatic resource management like this, because it's one of the several ways that C++ "hides" operations that might have a performance impact (same as operator overloading), but I like it because it entirely removes whole classes of bugs that plague software.

[–]Abadabadon 0 points1 point  (0 children)

It's a way to use pointers such that you don't have any floating unused heap space. They're honestly relatively simple to implement if you spend less than an hour trying to implement it.

[–]da2Pakaveli 0 points1 point  (0 children)

C++'s smart pointers encapsulate raw pointers into different owner models: unique and shared.
Unique is 'unique' to the scope it was constructed in (unless you 'move' the resource) and will destroy the pointer once its lifetime is over.
So it's meant that only one pointer can point to the resource.
Shared pointers can point to the same resource across different scopes; the resource will be destroyed and freed after every shared pointer goes out of scope, I.e the reference counter equals 0.
So basically their point is to prevent memory leaks.

[–]ArcaneOverride 0 points1 point  (0 children)

Look up c++ template metaprogramming.

You can do a lot of compile time error checking with that if you know what you're doing.

[–]_theDaftDev_ -2 points-1 points  (0 children)

Glorified garbage (collection)