如何在将原始指针移动到基类构造函数之前从unique_ptr中提取原始指针

How can I extract a raw pointer from a unique_ptr before moving it into my base class constructor

本文关键字:指针 原始 unique 提取 ptr 基类 移动 构造函数      更新时间:2023-10-16

我有一个非常具体的需求来访问特定于派生类的功能,我在构造包含类时unique_ptr获得了派生类的实例。然后,此包含类必须将unique_ptr向上转换为其基类,移动到包含类的基类构造函数,最终在该包含基类中moved 以获得所有权。一些代码应该会有所帮助:

class MemberBase {};
class MemberDerived : public MemberBase { /*some public stuff not in MemberBase*/ };
class MainBase {
std::unique_ptr<MemberBase> member_;
public:
MainBase(std::unique_ptr<MemberBase> member) : member_(std::move(member)) {}
};
class MainDerived : public MainBase {
MemberDerived* member_derived_;
// This class proceeds to use MemberDerived-only functions
public:
// How to write the initialization list below?
MainDerived(std::unique_ptr<MemberDerived> member)
: MainBase(std::move(member)), member_derived_(member.get() /*nullptr!!*/) {}
};

忽略整体类设计的问题(我知道与MainDerived中特定于MemberDerived函数的耦合并不理想;这是继承的代码,如果不进行重大重构就无法更改(,如何在将unique_ptr转发到MainBase之前抓住原始指针?

在下面找到一些我想到的想法,以及为什么我认为它们不是很好。

  1. 向下投射保护的访问器:
// Add this method to the protected section of MainBase:
MemberBase* MainBase::get_member() { return member_.get(); }
// Then downcast in MainDerived's c'tor
MainDerived::MainDerived(std::unique_ptr<MemberDerived> member)
: MainBase(std::move(member)), member_derived_(dynamic_cast<MemberDerived*>(get_member())) {}

这应该有效,但使用dynamic_cast(本身就是主要缺点(,当然,如果有人将传递给 c'tor 的类型更改为不是派生自MemberDerived的类型,它将在没有编译器帮助的情况下中断。

  1. 指针中传递两次:
// member and member_derived must point to the same object!
MainDerived::MainDerived(std::unique_ptr<MemberDerived> member, MemberDerived* member_derived)
: MainBase(std::move(member)), member_derived_(member_derived) {}

除了制作一个非常丑陋的 c'tor 签名之外,用户很容易为两个参数传递不同的指针或在调用get之前执行move。此外,用户现在被迫创建一个局部变量以将其传递到两个地方。我有没有提到它很丑?

  1. 使用辅助函数技巧翻转初始化顺序:
template <typename T>
std::unique_ptr<T> ExtractPointer(std::unique_ptr<T> p, T** target) {
*target = p.get();
return std::move(p);
}
MainDerived::MainDerived(std::unique_ptr<MemberDerived> member)
: MainBase(ExtractPointer(std::move(member), &member_derived_)) {}

现在我实际上有点惊讶这没有产生任何警告/错误(带有-Wall的 gcc 5.4.0 (。我的一部分喜欢这个,因为它似乎很安全,因为它很难打破,但member_derived_的迂回初始化让我有点不寒而栗。

您的第二个解决方案已经完成了一半。请注意,构造函数不需要public。您可以将 2 参数设为一个private,并编写委托给它的 1 参数public构造函数。

class MainDerived : public MainBase {
MemberDerived* member_derived_;
MainDerived(std::unique_ptr<MemberDerived> &member, MemberDerived *member_derived)
: MainBase(std::move(member)), member_derived_(member_derived) { }
public:
MainDerived(std::unique_ptr<MemberDerived> member)
: MainDerived(member, member.get()) { }
};

member必须在private构造函数中通过引用来获取,因为未指定函数调用参数的计算顺序。因此,您无法安全地将memberpublic构造函数移动到private构造函数的参数中,因为这样移动和get()的顺序将未指定,但我们需要在移动之前get()发生。最简单的解决方法是让private构造函数通过引用获取member,这不会改变它。(您也可以使用{}而不是()来强制实施计算顺序,但让您的代码依赖于这种微妙的结构并不是一个好主意。

既然你知道指针指向MemberDerived,你就不需要dynamic_cast。一个static_cast就可以了。

如果您担心在更改某些内容时在类型中出错,那么只需参考元素类型:

MainDerived(std::unique_ptr<MemberDerived> member)
: MainBase(std::move(member))
, member_derived_(static_cast<decltype(member)::element_type*>(member_.get())) 
{}

正如你提到的,这需要member_protectedprotected才能存在。

据我所知,这不会导致未定义的行为,除非有人传递了一个实际上并不指向与其element_type相同或派生类型的对象的std::unique_ptr,在这种情况下,使用此std::unique_ptr本身是不安全的,因为如果实例在没有事先移动的情况下被销毁,它会导致未定义的行为。


至于你的 3. 建议的方法:

我认为这在 C++17 中在技术上是允许的,因为member_derived_具有空的初始化,因此它的生命周期从分配存储时开始,而不是在其初始化完成时开始。

但是当前的 C++20 草案删除了这个例外,因此member_derived_的生命周期将仅在其空洞初始化后开始,这使得它使用未定义的行为。