如何在C++中使用基类的构造函数和赋值运算符?

How to use base class's constructors and assignment operator in C++?

提问人:Igor 提问时间:8/4/2009 最后编辑:AzeemIgor 更新时间:4/24/2018 访问量:87627

问:

我有一个带有一组构造函数和一个赋值运算符的类。B

在这里:

class B
{
 public:
  B();
  B(const string& s);
  B(const B& b) { (*this) = b; }
  B& operator=(const B & b);

 private:
  virtual void foo();
  // and other private member variables and functions
};

我想创建一个继承类,该类将覆盖函数,并且不需要进行其他更改。Dfoo()

但是,我希望拥有相同的构造函数集,包括复制构造函数和赋值运算符:DB

D(const D& d) { (*this) = d; }
D& operator=(const D& d);

我是否必须将它们全部重写,或者有没有办法使用 的构造函数和运算符?我特别想避免重写赋值运算符,因为它必须访问所有私有成员变量。DBB

C++ 继承 构造函数 Assignment-Operator

评论

0赞 anton_rh 3/1/2018
如果只想重写该方法,则可以使用 to 继承赋值运算符,但不能继承复制和移动构造函数:stackoverflow.com/q/49045026/5447906foousing B::operator=;

答:

20赞 Martin York 8/4/2009 #1

简短的回答:是的,您需要重复 D 中的工作

长答案:

如果派生类“D”不包含新成员变量,则默认版本(由编译器生成)应该可以正常工作。默认的 Copy 构造函数将调用父复制构造函数,默认赋值运算符将调用父赋值运算符。

但是,如果你的类“D”包含资源,那么你将需要做一些工作。

我发现你的复制构造函数有点奇怪:

B(const B& b){(*this) = b;}

D(const D& d){(*this) = d;}

通常,复制构造函数链,以便它们从基础开始复制构造。在这里,由于您调用的是赋值运算符,因此复制构造函数必须调用默认构造函数,以默认首先从下到上初始化对象。然后,使用赋值运算符再次向下。这似乎效率很低。

现在,如果你做一个作业,你是自下而上(或自上而下)复制的,但你似乎很难做到这一点,并提供一个强有力的例外保证。如果在任何时候资源复制失败,并且您抛出异常,则对象将处于不确定状态(这是一件坏事)。

通常我看到它以相反的方式完成。
赋值运算符是根据 copy 构造函数和 swap 定义的。这是因为它使提供强异常保证变得更加容易。我不认为你能够通过这样做来提供强有力的保证(我可能是错的)。

class X
{
    // If your class has no resources then use the default version.
    // Dynamically allocated memory is a resource.
    // If any members have a constructor that throws then you will need to
    // write your owen version of these to make it exception safe.


    X(X const& copy)
      // Do most of the work here in the initializer list
    { /* Do some Work Here */}

    X& operator=(X const& copy)
    {
        X tmp(copy);      // All resource all allocation happens here.
                          // If this fails the copy will throw an exception 
                          // and 'this' object is unaffected by the exception.
        swap(tmp);
        return *this;
    }
    // swap is usually trivial to implement
    // and you should easily be able to provide the no-throw guarantee.
    void swap(X& s) throws()
    {
        /* Swap all members */
    }
};

即使您从 X 派生出类 D,也不会影响此模式。
诚然,您需要通过对基类进行显式调用来重复一些工作,但这相对微不足道。

class D: public X
{

    // Note:
    // If D contains no members and only a new version of foo()
    // Then the default version of these will work fine.

    D(D const& copy)
      :X(copy)  // Chain X's copy constructor
      // Do most of D's work here in the initializer list
    { /* More here */}



    D& operator=(D const& copy)
    {
        D tmp(copy);      // All resource all allocation happens here.
                          // If this fails the copy will throw an exception 
                          // and 'this' object is unaffected by the exception.
        swap(tmp);
        return *this;
    }
    // swap is usually trivial to implement
    // and you should easily be able to provide the no-throw guarantee.
    void swap(D& s) throws()
    {
        X::swap(s); // swap the base class members
        /* Swap all D members */
    }
};

评论

1赞 Steve Jessop 8/5/2009
在与 X 相同的命名空间中添加一个 free swap 函数应该具有相同的效果(通过 ADL),但最近有人说 MSVC 错误地显式调用了 std::swap,从而使 dribeas 正确......
1赞 Steve Jessop 8/5/2009
您可以在 std 中专门化用户定义类型的标准算法。dribeas 的代码是有效的,只是大师们似乎推荐了 ADL 解决方案。
1赞 Martin York 5/12/2021
资源:你得到的东西,但必须(应该)明确地回馈。示例:内存/文件描述符/打开连接/锁等。
1赞 Martin York 5/12/2021
@AbhishekMane 如果你的类包含资源(你需要回馈的东西)。然后你需要有一个析构函数来归还它。如果您有析构函数,则默认的复制构造函数和赋值运算符将不起作用(您需要执行深层复制)。这被称为三法则。如果定义了其中任何一个(析构函数 CC 或 O=O),则必须定义所有三个。请搜索“Rule of Three”
1赞 Martin York 6/1/2021
@AbhishekMane 资源示例:动态分配的内存:;类型不是资源。类型不是资源;尽管它可能会在内部动态分配内存,但这对类来说是私有的(您不知道或不需要知道)。该类已经实现了相应的 CC O=O 析构函数等,因此它会自动且透明地处理所有这些内容。您可以将其视为一个简单的对象(如 int),因为它正确地实现了五法则。new int(5)intstd::stringstd::string
3赞 Luc Hermitte 8/4/2009 #2

你的设计很可能存在缺陷(提示:切片实体语义值语义)。通常根本不需要对来自多态层次结构的对象具有完整的副本/值语义。如果您想提供它以防万一以后可能需要它,这意味着您永远不会需要它。改为使基类不可复制(例如,通过继承 boost::noncopyable),仅此而已。

当这种需求真正出现时,唯一正确的解决方案是信封字母的成语,或者是 Sean Parent 和 Alexander Stepanov IIRC 关于规则对象的文章中的小框架。所有其他解决方案都会给您带来切片和/或 LSP 的麻烦。

关于这个主题,另请参阅 C++CoreReference C.67: C.67:基类应禁止复制,如果需要“复制”,则应提供虚拟克隆

2赞 David Rodríguez - dribeas 8/4/2009 #3

您必须重新定义所有非默认构造函数或复制构造函数。您不需要重新定义复制构造函数或赋值运算符,因为编译器提供的运算符(根据标准)将调用所有基版本:

struct base
{
   base() { std::cout << "base()" << std::endl; }
   base( base const & ) { std::cout << "base(base const &)" << std::endl; }
   base& operator=( base const & ) { std::cout << "base::=" << std::endl; }
};
struct derived : public base
{
   // compiler will generate:
   // derived() : base() {}
   // derived( derived const & d ) : base( d ) {}
   // derived& operator=( derived const & rhs ) {
   //    base::operator=( rhs );
   //    return *this;
   // }
};
int main()
{
   derived d1;      // will printout base()
   derived d2 = d1; // will printout base(base const &)
   d2 = d1;         // will printout base::=
}

请注意,正如 sbi 所指出的,如果您定义了任何构造函数,编译器将不会为您生成默认构造函数,其中包括复制构造函数。

评论

1赞 sbi 8/4/2009
请注意,如果定义了任何其他 ctor(包括复制 ctor),编译器将不会提供默认 ctor。因此,如果你想有一个默认的 ctor,你需要显式定义一个。derived
0赞 Abhishek Mane 5/11/2021
@DavidRodriguez-dribeas,它不是任何构造函数,而是任何复制构造函数as sbi noted, if you define any constructor the compiler
146赞 Motti 8/4/2009 #4

可以显式调用构造函数和赋值运算符:

class Base {
//...
public:
    Base(const Base&) { /*...*/ }
    Base& operator=(const Base&) { /*...*/ }
};

class Derived : public Base
{
    int additional_;
public:
    Derived(const Derived& d)
        : Base(d) // dispatch to base copy constructor
        , additional_(d.additional_)
    {
    }

    Derived& operator=(const Derived& d)
    {
        Base::operator=(d);
        additional_ = d.additional_;
        return *this;
    }
};

有趣的是,即使您没有显式定义这些函数(然后它使用编译器生成的函数),这也有效。

class ImplicitBase { 
    int value_; 
    // No operator=() defined
};

class Derived : public ImplicitBase {
    const char* name_;
public:
    Derived& operator=(const Derived& d)
    {
         ImplicitBase::operator=(d); // Call compiler generated operator=
         name_ = strdup(d.name_);
         return *this;
    }
};  

评论

0赞 qed 7/13/2013
这是什么意思?Base(const Base&)
2赞 Motti 7/14/2013
@CravingSpirit它是一个复制构造函数(省略了参数名称)。
2赞 Motti 7/14/2013
@CravingSpirit它们在不同的情况下使用,但这是基本的C++我建议您多阅读一些相关信息。
1赞 Bin 4/19/2016
@qed复制构造函数用于初始化,而赋值运算符用于赋值表达式。
1赞 Motti 7/25/2023
@MackieMesser只需要在处理模板(特别是转发引用)时使用,但这里的情况并非如此。无论如何,这个答案是从 2009 年开始的,在 C++11 之前出来的。(见 en.cppreference.com/w/cpp/utility/forwardstd::forward()std::forward())
0赞 Mario Galindo 1/1/2015 #5

原始代码错误:

class B
{
public:
    B(const B& b){(*this) = b;} // copy constructor in function of the copy assignment
    B& operator= (const B& b); // copy assignment
 private:
// private member variables and functions
};

通常,不能根据复制分配来定义复制构造函数,因为复制分配必须释放资源,而复制构造函数不会!!

要理解这一点,请考虑:

class B
{
public:
    B(Other& ot) : ot_p(new Other(ot)) {}
    B(const B& b) {ot_p = new  Other(*b.ot_p);}
    B& operator= (const B& b);
private:
    Other* ot_p;
};

为避免内存泄漏,复制分配首先必须删除 ot_p 指向的内存:

B::B& operator= (const B& b)
{
    delete(ot_p); // <-- This line is the difference between copy constructor and assignment.
    ot_p = new  Other(*b.ot_p);
}
void f(Other& ot, B& b)
{
    B b1(ot); // Here b1 is constructed requesting memory with  new
    b1 = b; // The internal memory used in b1.op_t MUST be deleted first !!!
}

因此,复制构造函数和复制赋值是不同的,因为前者将构造和对象构造到初始化的内存中,而后者则必须先释放现有内存,然后才能构造新对象。

如果执行本文中最初建议的操作:

B(const B& b){(*this) = b;} // copy constructor

您将删除一个不存在的内存。