问题
First take a look at what C++ Primer said about unique_ptr
and shared_ptr
:
$16.1.6. Efficiency and Flexibility
We can be certain that
shared_ptr
does not hold the deleter as a direct member, because the type of the deleter isn’t known until run time.Because the type of the deleter is part of the type of a
unique_ptr
, the type of the deleter member is known at compile time. The deleter can be stored directly in eachunique_ptr
object.
So it seems like that the shared_ptr
does not have a direct member of deleter, but unique_ptr
does. However, the top-voted answer of another question says:
If you provide the deleter as template argument (as in
unique_ptr
) it is part of the type and you don't need to store anything additional in the objects of this type. If deleter is passed as constructor's argument (as inshared_ptr
) you need to store it in the object. This is the cost of additional flexibility, since you can use different deleters for the objects of the same type.
The two quoted paragraph are totally conflicting, which makes me confused. What's more, many people says unique_ptr is zero overhead because it doesn't need to store the deleter as member. However, as we know, unique_ptr
has a constructor of unique_ptr<obj,del> p(new obj,fcn)
, which means that we can pass a deleter to it, so unique_ptr
seems to have stored deleter as a member. What a mess!
回答1:
std::unique_ptr<T>
is quite likely to be zero-overhead (with any sane standard-library implementation). std::unique_ptr<T, D>
, for an arbitrary D
, is not in general zero-overhead.
The reason is simple: Empty-Base Optimisation can be used to eliminate storage of the deleter in case it's an empty (and thus stateless) type (such as std::default_delete
instantiations).
回答2:
The key phrase which seems to confuse you is "The deleter can be stored directly". But there's no point in storing a deleter of type std::default_delete
. If you need one, you can just create one as std::default_delete{}
.
In general, stateless deleters do not need to be stored, as you can create them on demand.
回答3:
Angew's answer explained pretty thoroughly what's going on.
For those curious how things could look under the covers
template<typename T, typename D, bool Empty = std::is_empty_v<D>>
class unique_ptr
{
T* ptr;
D d;
// ...
};
template<typename T, typename D>
class unique_ptr<T, D, true> : D
{
T* ptr;
// ...
};
Which specializes for empty deleters and take advantage of empty base optimization.
回答4:
Brief intro:
unique_ptr can introduce some small overhead, but not because of the deleter, but because when you move from it value must be set to null where if you were using raw pointers you could leave the old pointer in bug prone but legitimate state where it still points to where it pointed before. Obviously smart optimizer can optimize, but it is not guaranteed.
Back to the deleter:
Other answers are correct, but elaborate. So here is the simplified version witout mention of EBO or other complicated terms.
If deleter is empty(has no state) you do not need to keep it inside the unique_ptr. If you need it you can just construct it when you need it. All you need to know is the deleter type(and that is one of the template arguments for unique_ptr).
For exaple consider following code, than also demonstrates simple creation on demand of a stateless object.
#include <iostream>
#include <string>
#include <string_view>
template<typename Person>
struct Greeter{
void greet(){
static_assert(std::is_empty_v<Person>, "Person must be stateless");
Person p; // Stateless Person instance constructed on demand
std::cout << "Hello " << p() << std::endl;
}
// ... and not kept as a member.
};
struct Bjarne{
std::string_view operator()(){
return "Bjarne";
}
};
int main() {
Greeter<Bjarne> hello_bjarne;
hello_bjarne.greet();
}
来源:https://stackoverflow.com/questions/50001598/how-can-unique-ptr-have-no-overhead-if-it-needs-to-store-the-deleter