来自http://en.cppreference.com/w/cpp/memory/unique_ptr:
如果
T是某个基类的派生类(sic)B,那么std::unique_ptr<T>可以隐式转换为std::unique_ptr<B>.结果的默认删除器std::unique_ptr<B>将使用operator deleteB,导致未定义的行为,除非析构函数B是虚拟的.请注意,std::shared_ptr行为方式不同:std::shared_ptr<B>将对类型使用operator delete,T并且即使析构函数B不是虚拟的,也将正确删除所拥有的对象.
上面描述的破坏行为差异的理由是什么?我最初的猜测是性能?
同样有趣的std::shared_ptr<B>是T,如果析构函数B是非虚拟的,并且在我从上下文中可以看到无法调用,那么如何能够调用类型的析构函数std::shared_ptr<B>?
std::shared_ptr<X>已经有一堆原始的开销B*.
一个shared_ptr<X>基本维护4件事.它维护一个指针B,它保持两个引用计数(一个"硬"引用计数,一个"软"引用计数weak_ptr),并保持一个清理功能.
清理功能是为什么shared_ptr<X>表现不同.当你创建一个shared_ptr<X>函数时,一个调用该特定类型的析构函数被创建并存储在由该函数管理的清理函数中shared_ptr<X>.
更改托管(B*变为C*)类型时,清理功能保持不变.
因为shared_ptr<X>需要管理引用计数,所以清理函数存储的额外开销是微不足道的.
对于a来说unique_ptr<B>,这个班级几乎和原始班级一样便宜B*.它保持除了它之外的零状态B*,并且它的行为(在破坏时)归结为if (b) delete b;.(是的,这if (b)是多余的,但优化器可以解决这个问题).
为了支持铸-底部和删除,为衍生,额外的状态必须被存储在记忆中unique_ptr是真的派生类.这可以是存储的指向删除指针的形式,如a shared_ptr.
但是,这会使a的大小加倍unique_ptr<B>,或者要求它在某个地方将数据存储在堆上.
决定unique_ptr<B>应该是零开销,因此它不支持cast-to-base同时仍然调用base的析构函数.
现在,你可以unique_ptr<B>通过简单地添加一个删除器类型并存储一个知道它正在销毁的东西的类型的销毁函数来教导这样做.以上一直在谈论默认删除unique_ptr,这是无状态和无关紧要的.
struct deleter {
void* state;
void(*f)(void*);
void operator()(void*)const{if (f) f(state);}
deleter(deleter const&)=default;
deleter(deleter&&o):deleter(o) { o.state = nullptr; o.f=nullptr; }
deleter()=delete;
template<class T>
deleter(T*t):
state(t),
f([](void*p){delete static_cast<T*>(p);})
{}
};
template<class T>
using smart_unique_ptr = std::unique_ptr<T, deleter>;
template<class T, class...Args>
smart_unique_ptr<T> make_smart_unique( Args&&... args ) {
T* t = new T(std::forward<Args>(args)...);
return { t, t };
}
Run Code Online (Sandbox Code Playgroud)
实例,我生成一个unique-ptr派生,将它存储在unique-ptr to base中,然后重置base.派生指针被删除.
(一个简单的void(*)(void*)删除器可能遇到问题,传入void*的基础和派生案例之间的值会有所不同.)
请注意,更改存储在此类中的指针unique_ptr而不更改删除操作将导致不正常的行为.
| 归档时间: |
|
| 查看次数: |
237 次 |
| 最近记录: |