Garbage collection basically just means that your allocated objects are automatically released when they're not being referenced any more.
More accurately, they're released when they become unreachable for the program, as circularly referenced objects would never get released otherwise.
Smart pointers just refer to any structure that behaves like an ordinary pointer but has some extra functionality attached. These include but are not limited to deallocation, but also copy-on-write, bound checks, ...
Now, as you have stated, smart pointers can be used to implement a form of garbage collection.
But the train of thought goes the following way:
- Garbage collection is a cool thing to have, as it's convenient and I have to take care of fewer things
- Therefore: I want garbage collection in my language
- Now, how can get GC into my language?
Of course, you can design it like this from start. C# was designed to be garbage collected, so just
new your object and it'll be released when the references fall out of scope. How this is done is up to the compiler.
But in C++, there was no garbage collection intended. If we allocate some pointer
int* p = new int; and it falls out of scope,
p itself is removed from the stack, but nobody takes care of the allocated memory.
Now the only thing you have from start are deterministic destructors. When an object leaves the scope it has been created in, its destructor is called. In combination with templates and operator overloading, you can design a wrapper object that behaves like a pointer, but uses destructor functionality to clean up resources attached to it (RAII). You call this one a smart pointer.
This is all highly C++ specific: Operator overloading, templates, destructors, ... In this particular language situation, you have developed smart pointers to provide you with the GC you want.
But if you design a language with GC from start, this is merely an implementation detail. You just say object will be cleaned up and the compiler will do this for you.
Smart pointers like in C++ wouldn't probably be even possible in languages like C#'s, which have no deterministic destruction at all (C# works around this by providing syntactic sugar for calling a
.Dispose() on certain objects). Unreferenced resources will finally be reclaimed by the GC, but it undefined when exactly this will happen.
And this, in turn, can allow the GC to do its work more efficient. Being built in deeper into the language than smart pointers, which are set on top of it, the .NET GC can e.g. delay memory operations and perform them in blocks to make them cheaper or even move memory around for increasing efficiency based on how often objects are accessed.