std::unique_ptr,默认复制构造函数和抽象类
std::unique_ptr, Default Copy Constructor, and Abstract Class
我有一个类表示一个使用唯一指针的树对象,一些节点组成树,以及一个函数,该函数根据一些参数构造指向抽象节点类的指针(它指向子类,因为抽象节点是抽象的)
class AbstractNode
{
vector<unique_ptr<AbstractNode>> children;
public:
AbstractNode(arguments...);
// other stuff...
};
class Tree
{
unique_ptr<AbstractNode> baseNode;
// other stuff...
}
unique_ptr<AbstractNode> constructNode(AbstractNodeTypes type);
将包含在树中的abstractNode的各种子类。子类为该类中的某些虚函数提供了不同的实现。
我希望能够通过创建一组具有相同类类型的新节点来复制我的树,这些节点是原始树中节点的不同副本。
问题是:
如果我为AbstractNode
类编写自己的复制构造函数来深度复制子类,我将不得不为AbstractNode
的所有子类编写复制构造函数,这似乎很烦人,因为唯一不能正确复制的是子指针。在这里使用复制构造函数也会很烦人,因为我认为在调用它们之前需要将子对象强制转换为正确的类型。
是否有办法让编译器让我使用默认的复制构造函数来设置除了子节点之外的所有内容?它可以把这些作为空指针吗?然后我可以写一个更简单的函数,它只是递归地添加子树来复制树。
如果这是不可能的,有没有任何人都知道的非丑陋的解决方案?
解决这个问题的典型方法是有一个类似Kerrek SB在他的回答中描述的虚拟clone
函数。然而,我不会费心编写您自己的value_ptr
类。它是更简单的只是重用std::unique_ptr
作为你的问题提出。它将需要AbstractNode
中的非默认复制构造函数,但不需要显式或不安全的强制转换:
class AbstractNode
{
std::vector<std::unique_ptr<AbstractNode>> children;
public:
AbstractNode() = default;
virtual ~AbstractNode() = default;
AbstractNode(AbstractNode const& an)
{
children.reserve(an.children.size());
for (auto const& child : an.children)
children.push_back(child->clone());
}
AbstractNode& operator=(AbstractNode const& an)
{
if (this != &an)
{
children.clear();
children.reserve(an.children.size());
for (auto const& child : an.children)
children.push_back(child->clone());
}
return *this;
}
AbstractNode(AbstractNode&&) = default;
AbstractNode& operator=(AbstractNode&&) = default;
// other stuff...
virtual
std::unique_ptr<AbstractNode>
clone() const = 0;
};
现在可以实现ConcreteNode
。它必须有一个有效的复制构造函数,该构造函数可能是默认的,具体取决于ConcreteNode
添加的数据成员。它必须实现clone()
,但这个实现是微不足道的:
class ConcreteNode
: public AbstractNode
{
public:
ConcreteNode() = default;
virtual ~ConcreteNode() = default;
ConcreteNode(ConcreteNode const&) = default;
ConcreteNode& operator=(ConcreteNode const&) = default;
ConcreteNode(ConcreteNode&&) = default;
ConcreteNode& operator=(ConcreteNode&&) = default;
// other stuff...
virtual
std::unique_ptr<AbstractNode>
clone() const override
{
return std::unique_ptr<AbstractNode>(new ConcreteNode(*this));
}
};
我建议让clone
返回一个unique_ptr
而不是一个原始指针,只是为了确保没有一个新的指针在没有所有者的情况下暴露的机会。
为了完整起见,我还展示了其他特殊成员的样子。
起初我认为c++ 14的make_unique
会很好地在这里使用。它可以用在这里。但我个人认为,在这个特殊的例子中,它真的没有发挥它的作用。下面是它的样子:
virtual
std::unique_ptr<AbstractNode>
clone() const override
{
return std::make_unique<ConcreteNode>(*this);
}
使用make_unique
,你必须首先构造一个unique_ptr<ConcreteNode>
,然后依赖于从它到unique_ptr<AbstractNode>
的隐式转换。这是正确的,并且一旦完全启用内联,额外的舞蹈可能将被优化掉。但是当你真正需要的是一个用新的ConcreteNode*
构造的unique_ptr<AbstractNode>
时,使用make_unique
似乎是一个不必要的混淆。
您可能希望推出自己的value_ptr
实现,而不是使用unique_ptr
。这些设计在过去经常被提出,但在我们有一个标准化的版本之前,要么推出你自己的,要么找到一个现有的实现。
它看起来像这样:
template <typename T> struct value_ptr
{
T * ptr;
// provide access interface...
explicit value_ptr(T * p) noexcept : ptr(p) {}
~value_ptr() { delete ptr; }
value_ptr(value_ptr && rhs) noexcept : ptr(rhs.ptr)
{ rhs.ptr = nullptr; }
value_ptr(value_ptr const & rhs) : ptr(rhs.clone()) {}
value_ptr & operator=(value_ptr && rhs) noexcept
{
if (&rhs != this) { delete ptr; ptr = rhs.ptr; rhs.ptr = nullptr; }
return *this;
}
value_ptr & operator=(value_ptr const & rhs)
{
if (&rhs != this) { T * p = rhs.clone(); delete ptr; ptr = p; }
return *this;
}
};
你可以从一组可克隆的节点构建你的树。
struct AbstractNode
{
virtual ~AbstractNode() {}
virtual AbstractNode * clone() const = 0;
std::vector<value_ptr<AbstractNode>> children;
};
struct FooNode : AbstractNode
{
virtual FooNode * clone() const override { return new FooNode(this); }
// ...
};
现在您的节点可以自动复制,而无需编写任何显式的复制构造函数。您所需要做的就是通过在每个派生类中重写clone
来维护规则。
通常的模式是在层次结构中使用虚拟克隆方法。
如果这是不可能的,有没有任何人都知道的非丑陋的解决方案?
还可以使用基于复制构造函数的克隆函数的模板实例化。以下是我在编写web服务器(宠物项目)时使用的解决方案:
#pragma once
#include <memory>
#include <cassert>
#include <functional>
#include <stdexcept>
#include <vector>
namespace stdex {
inline namespace details {
/// @brief Deep copy construct from (Specialized&)*src
///
/// @retval nullptr if src is nullptr
/// @retval Specialized clone of *src
///
/// @note Undefined behavior src does not point to a Specialized*
template<typename Base, typename Specialized>
Base* polymorphic_clone (const Base* src) {
static_assert(std::is_base_of<Base, Specialized>::value,
"Specialized is not a specialization of Base");
if (src == nullptr)
return nullptr;
return new Specialized{ static_cast<const Specialized&>(*src) };
}
}
/// @brief polymorphic reference interface over a base class
///
/// Respects polymorphic behavior of class ref.
/// Instances have deep copy semantics (clone) and
/// "[const] Base&" interface
///
/// @note Not regular: no trivial way to implement non-intrusive equality
///
/// @note safe to use with standard containers
template<typename Base>
class polymorphic final
{
public:
/// Functor capable to convert a Base* to it's specialized type
/// and clone it (intrusive implementation can be used)
typedef std::function<Base* (const Base*)> clone_functor;
/// @brief construct (takes ownership of ptr)
template<typename Specialized, typename CloneSpecialized>
polymorphic(Specialized* ptr, CloneSpecialized functor) noexcept
: instance_{ptr}, clone_{std::move(functor)}
{
static_assert(std::is_base_of<Base, Specialized>::value,
"Specialized is not a specialization of Base");
static_assert(
std::is_constructible<clone_functor, CloneSpecialized>::value,
"CloneSpecialized is not valid for a clone functor");
}
// not implemented: UB cloning in case client provides specialized ptr
// polymorphic(Base* ptr);
polymorphic()
: polymorphic{ nullptr, clone_functor{} }
{
}
polymorphic(polymorphic&&) = default;
polymorphic(const polymorphic& other)
: polymorphic{std::move(other.clone())}
{
}
polymorphic& operator=(polymorphic other)
{
std::swap(instance_, other.instance_);
std::swap(clone_, other.clone_);
return *this;
}
~polymorphic() = default;
/// @brief Cast to contained type
/// @pre instance not moved
/// @pre *this initialized with valid instance
operator Base&() const
{
assert(instance_.get());
return *instance_.get();
}
/// @brief Cast to contained type
/// @pre instance not moved
/// @pre *this initialized with valid instance
operator const Base&() const
{
assert(instance_.get());
return *instance_.get();
}
private:
polymorphic clone() const
{
return polymorphic{
clone_(instance_.get()), clone_functor{clone_}
};
}
std::unique_ptr<Base> instance_;
clone_functor clone_;
};
template<typename Base, typename Specialized, typename CF>
polymorphic<Base> to_polymorphic(Specialized&& temp, CF functor)
{
static_assert(std::is_base_of<Base, Specialized>::value,
"Specialized is not a specialization of Base");
typedef typename polymorphic<Base>::clone_functor clone_functor;
auto ptr_instance = std::unique_ptr<Base>{
new Specialized{std::move(temp)}
};
auto clone_instance = clone_functor{std::move(functor)};
return polymorphic<Base>{ptr_instance.release(), clone_instance};
}
template<typename Base, typename Specialized>
polymorphic<Base> to_polymorphic(Specialized&& temp)
{
static_assert(std::is_base_of<Base, Specialized>::value,
"Specialized is not a specialization of Base");
return to_polymorphic<Base,Specialized>(
std::move(temp), details::polymorphic_clone<Base,Specialized>
);
}
template<typename Base, typename Specialized, typename ...Args>
polymorphic<Base> to_polymorphic(Args ...args)
{
static_assert(std::is_constructible<Specialized, Args...>::value,
"Cannot instantiate Specialized from arguments");
return to_polymorphic<Base,Specialized>(
std::move(Specialized{std::forward<Args...>(args...)}));
}
template<typename Base> using polymorphic_vector =
std::vector<polymorphic<Base>>;
template<typename Base, typename ...Args>
polymorphic_vector<Base> to_polymorphic_vector(Args&& ...args)
{
return polymorphic_vector<Base>{to_polymorphic<Base>(args)...};
}
} // stdex
使用例子:
stdex::polymorphic_vector<view> views = // explicit type for clarity
stdex::to_polymorphic_vector(
echo_view{"/echo"}, // class echo_view : public view
directory_view{"/static_files"} // class directory_view : public view
);
for(auto& v: views)
if(v.matches(reuqest.url())) // bool view::matches(...);
auto response = view.handle(request); // virtual view::handle(...) = 0;
限制:
如果你使用多重继承,不要使用stdex::details::polymorphic_clone。写一个基于dynamic_cast的实现,并使用to_polymorphic(Specialized&& temp, CF functor)
。
如果你想对类的一部分使用默认行为,而只对其余部分使用非标准行为来增强它,请考虑在功能和组织上拆分类:
将所有需要默认行为的元素放入它们自己的子对象中(继承的或合成的),这样就可以很容易地为它们使用default-special-function,并将其余的元素添加到子对象之外。
的实现留给感兴趣的读者作为练习。
- C++17复制构造函数,在std::unordereded_map上进行深度复制
- 为什么在C++中使用私有复制构造函数与删除复制构造函数
- 当从函数参数中的临时值调用复制构造函数时
- 如果有一个模板构造函数只有一个泛型参数,为什么我必须有一个复制构造函数
- 为什么需要复制构造函数,在哪些情况下它们非常有用
- 使用仅使用一次的变量调用的复制构造函数.这可能是通过调用move构造函数进行编译器优化的情况吗
- 为什么类中的ostringstream类型的成员会导致";调用隐含删除复制构造函数";错误
- 复制构造函数、赋值运算符C++
- std::ofstream 作为类成员删除复制构造函数?
- 复制构造函数C++无法正确复制指针
- 关于复制构造函数的一个棘手问题
- 为什么调用复制构造函数而不是移动构造函数?
- 填充上编译器生成的复制构造函数之间的不一致
- C++ 对象指针数组的复制构造函数
- C++ 基本 CTOR 说明 - 为什么不调用赋值/复制构造函数
- 防止在复制构造函数中隐式调用基构造函数
- 为用户定义的类正确调用复制构造函数/赋值运算符
- 具有已删除移动和复制构造函数的类的就地构造
- 复制构造函数隐式转换问题
- 复制构造函数中的递归调用