【问题标题】:Unique Pointers and The Rule of 3唯一指针和 3 规则
【发布时间】:2018-12-21 16:34:56
【问题描述】:

当我想要多态行为时,我经常发现自己在 C++ 中使用唯一指针。我通常会实现类似下面的纯抽象类:

class A { 
public:
    virtual A* clone() const = 0; // returns a pointer to a deep copy of A
    // other methods go here
};

当我想用自己的 A 实例修饰另一个类时,clone 方法会派上用场,例如:

#include <memory>

class B {
private:
    std::unique_ptr<A> a_ptr;
public:
    // ctor
    B(const A& a) {
        a_ptr = std::unique_ptr<A>(a.clone());
        //...
    }
    // copy ctor
    B(const B& other) : B(*other.a_ptr) {}
};

我总是最终在 B 中实现复制构造函数以避免编译器错误(MSVC 给出了关于尝试引用已删除函数的模糊消息),由于唯一指针,这完全有意义。我的问题可以总结如下:

  1. 我真的需要 B 中的复制构造函数吗?也许有更好的模式可以让我完全避免它。

  2. 如果是 1,我可以停在那里吗?我是否需要实现其他默认功能? IE。在任何情况下我还需要默认构造函数和析构函数吗?

在实践中,每当我觉得需要实现默认函数时,我通常会在其他三个函数旁边实现一个移动构造函数;我通常使用复制和交换习语(根据GManNickG's answer in this thread)。我认为这不会改变任何事情,但也许我错了!

非常感谢!

【问题讨论】:

  • 拥有一个独特的资源。它不会因为编译器错误而违反规则 3(或 5)。但是,如果您需要缺少的方法提供的功能,您有 2 个选择:(1)将方法添加到每个类; (2) 编写一个my_sort_of_unique_ptr 类,该类实现一个复制构造函数,该构造函数可以进行克隆(您可能还需要移动)并使用它而不是std::unique_ptr
  • 谢谢理查德;我非常喜欢你的建议(2)。我认为定制的唯一指针类将是一个非常优雅的解决方案。

标签: c++ polymorphism unique-ptr


【解决方案1】:

首先,我认为您的克隆函数的签名可能是

virtual std::unique_ptr<A> clone() = 0;

因为您想要 A 实例的深层副本和 B 内的独占所有权。其次,当您希望类可复制时,您确实必须为您的类定义一个复制构造函数。赋值运算符也是如此。这是因为std::unique_ptr 是只移动类型,这会阻碍编译器生成默认实现。

不需要其他特殊的成员函数,尽管它们可能有意义。编译器不会为您生成移动构造函数和移动赋值运算符(当您发布自己的复制/赋值函数时),但在您的情况下,您可以轻松地= default; 它们。析构函数同样可以用= default; 定义,这与core guidelines 一致。

请注意,通过= default 定义析构函数应该在翻译单元中完成,因为std::unique_ptr 需要在释放其资源时知道完整类型。

您是否需要默认构造函数完全取决于您要如何使用 B 类。

【讨论】:

  • 感谢 lubgr 的解释。我知道使用唯一指针进行复制和赋值的限制,但不知道仅移动类型的概念。我从来没有遇到过'= default;'之前,但我可以看到自己经常使用它。
【解决方案2】:

正如@lubgr 在他的回答中提到的那样,您应该从clone 函数返回unique_ptr 而不是原始的。无论如何,去你的问题:

  1. B 中是否需要复制构造函数?好吧,这取决于您的用例,但是如果您复制 B 类的对象,您可能需要一个。但正如你所说,你经常这样做,所以考虑更通用的方法是明智的。其中之一是为unique_ptr 创建一个包装器,该包装器将具有复制构造函数,并将在此复制构造函数中制作此指针的深层副本。 考虑以下示例:

    template<class T>
    class unique_ptr_wrap {
    public:
        unique_ptr_wrap(std::unique_ptr< T > _ptr) : m_ptr(std::move(_ptr)){}
    
        unique_ptr_wrap(const unique_ptr_wrap &_wrap){
            m_ptr = _wrap->clone();
        }
    
        unique_ptr_wrap(unique_ptr_wrap &&_wrap){
            m_ptr = std::move(_wrap.m_ptr);
        }
    
        T *operator->() const {
            return m_ptr.get();
        }
    
        T &operator*() const {
            return *m_ptr;
        }
    private:
        std::unique_ptr< T > m_ptr;
    };
    
  2. 这又取决于您的需要。我个人也建议重载移动构造函数,以使其使用较少的动态分配(但这可能是过早的优化,这是万恶之源)。

【讨论】:

  • 谢谢巴托。当然你是对的,我是否需要 B 的复制构造函数取决于它是否实际使用!在我的情况下,大多数课程(希望)会被其他人使用,所以我会假设是这样。我认为您的答案与我将实现的非常接近:我将为可克隆类型添加一个赋值运算符和一个基类(并确保 T 派生自可克隆基类)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-21
  • 2015-02-02
  • 2017-11-08
  • 1970-01-01
  • 1970-01-01
  • 2014-07-13
相关资源
最近更新 更多