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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1226634/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:13:23  来源:igfitidea点击:

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

c++inheritanceconstructorassignment-operator

提问by Igor Oks

I have a class Bwith a set of constructors and an assignment operator.

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

Here it is:

这里是:

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
};

I want to create an inheriting class Dthat will just override the function foo(), and no other change is required.

我想创建一个D只覆盖函数的继承类,foo()不需要其他更改。

But, I want Dto have the same set of constructors, including copy constructor and assignment operator as B:

但是,我希望D拥有与以下相同的构造函数集,包括复制构造函数和赋值运算符B

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

Do I have to rewrite all of them in D, or is there a way to use B's constructors and operator? I would especially want to avoid rewriting the assignment operator because it has to access all of B's private member variables.

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

回答by Motti

You can explicitly call constructors and assignment operators:

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

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;
    }
};

The interesting thing is that this works even if you didn't explicitly define these functions (it then uses the compiler generated functions).

有趣的是,即使您没有明确定义这些函数(然后它使用编译器生成的函数),这也能工作。

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;
    }
};  

回答by Martin York

Short Answer: Yes you will need to repeat the work in D

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

Long answer:

长答案:

If your derived class 'D' contains no new member variables then the default versions (generated by the compiler should work just fine). The default Copy constructor will call the parent copy constructor and the default assignment operator will call the parent assignment operator.

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

But if your class 'D' contains resources then you will need to do some work.

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

I find your copy constructor a bit strange:

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

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

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

Normally copy constructors chain so that they are copy constructed from the base up. Here because you are calling the assignment operator the copy constructor must call the default constructor to default initialize the object from the bottom up first. Then you go down again using the assignment operator. This seems rather inefficient.

通常复制构造函数链使它们从基础向上复制构造。在这里,因为您正在调用赋值运算符,所以复制构造函数必须首先调用默认构造函数以从底向上默认初始化对象。然后你再次使用赋值运算符下降。这似乎相当低效。

Now if you do an assignment you are copying from the bottom up (or top down) but it seems hard for you to do that and provide a strong exception guarantee. If at any point a resource fails to copy and you throw an exception the object will be in an indeterminate state (which is a bad thing).

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

Normally I have seen it done the other way around.
The assignment operator is defined in terms of the copy constructor and swap. This is because it makes it easier to provide the strong exception guarantee. I don't think you will be able to provide the strong guarantee by doing it this way around (I could be wrong).

通常我看到它以相反的方式完成。
赋值运算符是根据复制构造函数和交换定义的。这是因为它可以更容易地提供强大的异常保证。我认为您无法通过这种方式提供强有力的保证(我可能是错的)。

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 */
    }
};

Even if you derive a class D from from X this does not affect this pattern.
Admittedly you need to repeat a bit of the work by making explicit calls into the base class, but this is relatively trivial.

即使您从 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 */
    }
};

回答by Luc Hermitte

You most likely have a flaw in your design (hint: slicing, entity semanticsvs value semantics). Having a full copy/value semanticson an object from a polymorphic hierarchy is often not a need at all. If you want to provide it just in case one may need it later, it means you'll never need it. Make the base class non copyable instead (by inheriting from boost::noncopyable for instance), and that's all.

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

The only correct solutions when such need reallyappears are the envelop-letter idiom, or the little framework from the article on Regular Objectsby Sean Parent and Alexander Stepanov IIRC. All the other solutions will give you trouble with slicing, and/or the LSP.

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

On the subject, see also C++CoreReference C.67: C.67: A base class should suppress copying, and provide a virtual clone instead if "copying" is desired.

关于该主题,另请参见 C++CoreReference C.67:C.67:基类应禁止复制,并在需要“复制”时提供虚拟克隆

回答by David Rodríguez - dribeas

You will have to redefine all constructors that are not defaultor copyconstructors. You do not need to redefine the copy constructor nor assignment operator as those provided by the compiler (according to the standard) will call all the base's versions:

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

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::=
}

Note that, as sbi noted, if you define any constructor the compiler will not generate the default constructor for you and that includes the copy constructor.

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

回答by Mario Galindo

The original code is wrong:

原代码有误:

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
};

In general, you can not define the copy constructor in terms of the copy assignment, because the copy assignment must release the resources and the copy constructor don't !!!

一般情况下,复制赋值不能定义复制构造函数,因为复制赋值必须释放资源,而复制构造函数不释放!!!

To understand this, consider:

要理解这一点,请考虑:

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;
};

To avoid memory leak , the copy assignment first MUST delete the memory pointed by 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 !!!
}

So, copy constructor and copy assignment are different because the former construct and object into an initialized memory and, the later, MUST first release the existing memory before constructing the new object.

所以,复制构造函数和复制赋值是不同的,因为前者构造和对象进入初始化内存,后者必须在构造新对象之前先释放现有内存。

If you do what is originally suggested in this article:

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

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

you will be deleting an unexisting memory.

你将删除一个不存在的记忆。