C++ 复印及;基类和派生类中的交换

C++ 复印及;基类和派生类中的交换,c++,c++11,constructor,derived-class,base-class,C++,C++11,Constructor,Derived Class,Base Class,我最近读到并正在尝试在基类和派生类中实现这些构造函数。我的基类和派生类中都有四个构造函数,但是我不确定如何实现派生类的赋值运算符 explicit Base(int i) : m_i{i} {} Base(const Base & other) : m_i{other.m_i} Base(Base && other) : Base(0) { swap(*this, other); } Base & operator=(Base other) { swap(*thi

我最近读到并正在尝试在基类和派生类中实现这些构造函数。我的基类和派生类中都有四个构造函数,但是我不确定如何实现派生类的赋值运算符

explicit Base(int i) : m_i{i} {}
Base(const Base & other) : m_i{other.m_i}
Base(Base && other) : Base(0) { swap(*this, other); }
Base & operator=(Base other) { swap(*this, other); return *this; }
friend void swap(Base & a, Base & b) noexcept {
    using std::swap;
    swap(a.m_i, b.m_i);
}

explicit Derived(int j) : Base(42), m_j(j) {}
Derived(const Derived & other) : Derived(other.m_j) {}
Derived(Derived && other) : Derived(other.m_j) { swap(*this, other); }
Derived & operator=(Derived other) { /*???*/ }
friend void swap(Derived & a, Derived & b) noexcept {
    using std::swap;
    swap(a.m_j, b.m_j);
}

您实现
op=
的方式与
派生的
实现
Base的方式完全相同:

Derived& operator=(Derived other) { swap(*this, other); return *this; }
不过,我希望您了解按值传递参数的利弊:

  • 正面:所有值类别只需要一个函数
  • 下侧:第二次移动xvalues,移动prvalues所需副本之外的内容
其他需要考虑的问题:

  • 经验法则:单参数非复制/移动构造函数应该是显式的:您真的不想从
    int
    Base
    进行隐式转换
  • 您忘记为
    派生的
    重新实现
    交换
    (交换所有子对象,包括基本对象和成员对象)。如果
    Derived
    没有添加任何成员,您可能会放弃它

    • 考虑尽可能多地使用
      =default
      。如果我们讨论的是公共继承,那么实际上也需要一个虚拟析构函数

      以下是使用复制/交换样式时,您的
      基础
      的外观:

      class Base
      {
          int m_i;
      public:
          virtual ~Base() = default;
          Base(const Base& other) = default;
          Base& operator=(Base other) noexcept
          {
              swap(*this, other);
              return *this;
          }
          Base(Base&& other) noexcept
              : Base(0)
          {
              swap(*this, other);
          }
      
          explicit Base(int i) noexcept
              : m_i{i}
              {}
      
          friend void swap(Base& a, Base& b) noexcept
          {
              using std::swap;
              swap(a.m_i, b.m_i);
          }
      };
      
      唯一不同的是,我添加了虚拟析构函数,并对复制构造函数使用了
      =default

      现在,对于导出的

      class Derived
          : public Base
      {
          int m_j;
      public:
          Derived(const Derived& other) = default;
          Derived& operator=(Derived other) noexcept
          {
              swap(*this, other);
              return *this;
          }
          Derived(Derived&& other) noexcept
              : Derived(0)
          {
              swap(*this, other);
          }
      
          explicit Derived(int j) noexcept
              : Base(42)
              , m_j{j}
              {}
      
          friend void swap(Derived& a, Derived& b) noexcept
          {
              using std::swap;
              swap(static_cast<Base&>(a), static_cast<Base&>(b));
              swap(a.m_j, b.m_j);
          }
      };
      
      这里真正需要做的唯一工作是自定义构造函数和
      swap
      函数

      派生的
      更容易:

      class Derived
          : public Base
      {
          int m_j;
      public:
          explicit Derived(int j) noexcept
              : Base(42)
              , m_j{j}
              {}
      
          friend void swap(Derived& a, Derived& b) noexcept
          {
              using std::swap;
              swap(static_cast<Base&>(a), static_cast<Base&>(b));
              swap(a.m_j, b.m_j);
          }
      };
      
      派生类
      :公共基地
      {
      国际博物馆;
      公众:
      显式派生(int j)noexcept
      :底座(42)
      ,m_j{j}
      {}
      朋友无效掉期(衍生a、衍生b)无例外
      {
      使用std::swap;
      交换(静态广播(a)、静态广播(b));
      掉期(a.m_j,b.m_j);
      }
      };
      
      所有5个特殊成员都可以隐式默认

      我们不能在
      Base
      中默认它们,因为我们需要指定虚拟析构函数,它禁止生成移动成员,并且不推荐使用用户声明的析构函数生成复制成员。但是,由于我们不需要在
      派生的
      中声明析构函数,我们可以让编译器处理所有事情

      由于复制/交换的一大卖点是减少编码,因此具有讽刺意味的是,使用它实际上需要比让编译器默认特殊成员更多的编码


      当然,如果默认值做不到正确的事情,那么就不要使用它们。我只是说,在复制/交换之前,默认值应该是您的第一选择。

      重击规则:单参数非复制/移动构造函数应该是显式的:您真的不想从
      int
      隐式转换到
      Base
      。此外,您还需要在
      派生的
      中定义
      swap
      ,除非
      Base
      的是好的enough@Deduplicator:谢谢你的提示,我编辑了我的问题。@Jon:但我喜欢用拇指捶打;-)(谢谢)哈哈!C++确实有一些拇指规则,人们应该因为不跟随而被大打出手!所以你根本不调用任何基的向量,在派生中做所有必要的事情?@gartenriese:是的,至少不直接。(所有派生的构造函数都调用一个基本的构造函数,而派生的::swap需要调用Base::swap)啊,你的派生的move构造函数是完全错误的:它需要廉价地初始化对象,然后交换所有东西。(如果默认的ctor或swap不便宜,则需要对其进行自定义编码)。这就引出了一个问题:为什么你没有一个默认的ctor?(可能会为ctor(int)添加一个默认值)“我再次明确默认了复制构造函数。这纠正了您的版本中忽略复制基类的错误。”-这是否意味着默认复制ctor调用其基类的复制ctor?@gartenriese:这是正确的。所有默认的特殊成员将首先在基上迭代,然后在非静态数据成员上迭代,依次对每个基执行指定的操作。哦,除了析构函数以构造函数的相反顺序执行这个迭代。好的,谢谢!顺便说一句,我不能使用默认值,因为我必须复制一些OpenGL的东西。“派生的移动构造函数不需要移动或复制其他任何东西,因为它将与其他对象交换”,-不交换实际移动的东西吗?或者你的意思是“通过直接调用”?a如果使用复制和移动构造函数的默认版本,为什么需要实现交换?
      class Derived
          : public Base
      {
          int m_j;
      public:
          explicit Derived(int j) noexcept
              : Base(42)
              , m_j{j}
              {}
      
          friend void swap(Derived& a, Derived& b) noexcept
          {
              using std::swap;
              swap(static_cast<Base&>(a), static_cast<Base&>(b));
              swap(a.m_j, b.m_j);
          }
      };