我正在阅读最新的Overload(链接),并决定测试第8页的语句:
shared_ptr将在作用域退出时正确调用B的析构函数,甚至尽管A的析构函数不是虚拟的。
我使用的是Visual Studio 2013编译器v120:
#include <memory>
#include <iostream>
struct A {
~A() { std::cout << "Deleting A"; }
};
struct B : public A
{
~B() { std::cout << "Deleting B"; }
};
int main()
{
std::shared_ptr<A> ptr = std::make_shared<B>();
ptr.reset();
return 0;
}
这按预期工作,并打印出"删除删除A"
这篇文章似乎暗示这也应该适用于std::unique_ptr:
几乎没有必要管理自己的资源,所以要抵制实现自己的复制/分配/移动构造/移动的诱惑赋值/析构函数。
托管资源可以是内部资源类定义或类本身的实例。围绕标准容器和类模板重构代码像unique_ptr或shared_ptr将使您的代码更可读可维护。
但是,在更改时
std::shared_ptr<A> ptr = std::make_shared<B>();
至
std::unique_ptr<A> ptr = std::make_unique<B>();
程序将只输出"删除A"
我是不是误解了这篇文章,而且这种行为是标准的本意?这是MSVC编译器的错误吗?
shared_ptr
和unique_ptr
不同。make_shared
将在调用时创建一个类型擦除的deleter对象,而对于unique_ptr,deleter是该类型的一部分。因此,shared_ptr在调用deleter时知道真实类型,而unique_ptr
不知道。这使得unique_ptr
的效率大大提高,这就是它以这种方式实现的原因。
事实上,我觉得这篇文章有点误导人。我不认为用虚拟函数公开基类的复制构造函数是一个好建议,这听起来像是很多切片问题
考虑以下情况:
struct A{
virtual void foo(){ std::cout << "base"; };
};
struct B : A{
virtual void foo(){ std::cout << "derived"; };
};
void bar(A& a){
a.foo(); //derived
auto localA = a; //poor matanance porgrammer didn't notice that a is polymorphic
localA.foo(); //base
}
我个人主张非侵入性多态性http://isocpp.org/blog/2012/12/value-semantics-and-concepts-based-polymorphism-sean-parent对于任何新的高层来说,它都完全回避了这个问题。
此行为符合标准。
unique_ptr被设计为与普通的new/delete相比没有性能开销。
shared_ptr有开销的余量,所以它可以更智能。
根据标准[20.7.1.2.2,20.7.2.2.2],unique_ptr对get()返回的指针调用delete,而shared_ptr则删除它所持有的真实对象——它会记住要删除的正确类型(如果正确初始化),即使没有虚拟析构函数。
显然,shared_ptr并不是无所不知的,你可以通过向基本对象传递一个指针来欺骗它,让它表现得很糟糕,如下所示:
std::shared_ptr<Base> c = std::shared_ptr<Base> { (Base*) new Child() };
但是,无论如何,那样做都是愚蠢的。
您可以对unique_ptr
执行类似的操作,但由于其删除程序类型是静态确定的,因此需要静态维护正确的删除程序类型。即(Coliru现场演示):
// Convert given pointer type to T and delete.
template <typename T>
struct deleter {
template <typename U>
void operator () (U* ptr) const {
if (ptr) {
delete static_cast<T*>(ptr);
}
}
};
// Create a unique_ptr with statically encoded deleter type.
template <typename T, typename...Args>
inline std::unique_ptr<T, deleter<T>>
make_unique_with_deleter(Args&&...args) {
return std::unique_ptr<T, deleter<T>>{
new T(std::forward<Args>(args)...)
};
}
// Convert a unique_ptr with statically encoded deleter to
// a pointer to different type while maintaining the
// statically encoded deleter.
template <typename T, typename U, typename W>
inline std::unique_ptr<T, deleter<W>>
unique_with_deleter_cast(std::unique_ptr<U, deleter<W>> ptr) {
T* t_ptr{ptr.release()};
return std::unique_ptr<T, deleter<W>>{t_ptr};
}
// Create a unique_ptr to T with statically encoded
// deleter for type U.
template <typename T, typename U, typename...Args>
inline std::unique_ptr<T, deleter<U>>
make_unique_with_deleter(Args&&...args) {
return unique_with_deleter_cast<T>(
make_unique_with_deleter<U>(std::forward<Args>(args)...)
);
}
使用起来有点尴尬:
std::unique_ptr<A, deleter<B>> foo = make_unique_with_deleter<A, B>();
auto
改善了这种情况:
auto bar = make_unique_with_deleter<A, B>();
它并没有给你带来太多好处,因为动态类型就在unique_ptr
的静态类型中编码。如果您要随身携带动态类型,为什么不简单地使用unique_ptr<dynamic_type>
呢?我猜想这样的东西在泛型代码中可能有一些用处,但找到这样的例子留给读者练习。
std::shared_ptr
用来实现魔术的技术称为类型擦除。如果您正在使用gcc,请尝试查找文件bits/shared_ptr_base.h
并检查实现。我使用的是gcc 4.7.2。
unique_ptr
是为最小化开销而设计的,并且不使用类型擦除来记住它所持有的指针的实际类型。
这里有一个关于这个主题的伟大讨论:链接
EDIT:shared_ptr
的一个简单实现,展示了如何实现类型擦除。
#include <cstddef>
// A base class which does not know the type of the pointer tracking
class ref_counter_base
{
public:
ref_counter_base() : counter_(1) {}
virtual ~ref_counter_base() {}
void increase()
{
++counter_;
}
void release()
{
if (--counter_ == 0) {
destroy();
delete this;
}
}
virtual void destroy() = 0;
private:
std::size_t counter_;
};
// The derived class that actually remembers the type of
// the pointer and deletes the pointer on destroy.
template <typename T>
class ref_counter : public ref_counter_base
{
public:
ref_counter(T *p) : p_(p) {}
virtual void destroy()
{
delete p_;
}
private:
T *p_;
};
template <typename T>
class shared_ptr
{
public:
shared_ptr(T *p)
: p_(p)
, counter_(new ref_counter<T>(p))
{
}
// Y* should be implicitely convertable to T*,
// i.e. Y is derived from T
template <typename Y>
shared_ptr(Y &other)
: p_(other.get())
, counter_(other.counter())
{
counter_->increase();
}
~shared_ptr()
{
counter_->release();
}
T* get() { return p_; }
ref_counter_base* counter() { return counter_; }
private:
T *p_;
ref_counter_base *counter_;
};