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

发布于 2024-07-30 00:45:28 字数 677 浏览 7 评论 0原文

我有一个类 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
};

我想创建一个继承类 D ,它只会重写函数 foo() ,不需要其他任何更改。

但是,我希望 D 具有与 B 相同的一组构造函数,包括复制构造函数和赋值运算符:

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

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

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

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 D that will just override the function foo(), and no other change is required.

But, I want D to have the same set of constructors, including copy constructor and assignment operator as 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

陌上芳菲 2024-08-06 00:45:35

原来的代码是错误的:

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

您将删除不存在的内存。

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:

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.

旧时模样 2024-08-06 00:45:34

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

当这种需求真正出现时,唯一正确的解决方案是信封字母惯用法,或者来自 Sean Parent 的常规对象文章中的小框架和亚历山大·斯捷潘诺夫 IIRC。 所有其他解决方案都会给您带来切片和/或 LSP 的麻烦。

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

You most likely have a flaw in your design (hint: slicing, entity semantics vs value semantics). Having a full copy/value semantics on 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.

The only correct solutions when such need really appears are the envelop-letter idiom, or the little framework from the article on Regular Objects by Sean Parent and Alexander Stepanov IIRC. All the other solutions will give you trouble with slicing, and/or the 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.

二手情话 2024-08-06 00:45:34

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

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 所指出的,如果您定义任何构造函数,编译器将不会生成默认值为您提供的构造函数,其中包括复制构造函数。

You will have to redefine all constructors that are not default or copy constructors. 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.

被你宠の有点坏 2024-08-06 00:45:33

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

长回答:

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

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

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

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

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

通常复制构造函数链,以便它们是从基础向上复制构造的。 这里因为您正在调用赋值运算符,所以复制构造函数必须调用默认构造函数以首先从下到上默认初始化对象。 然后你再使用赋值运算符往下走。 这看起来效率相当低。

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

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

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

Short Answer: Yes you will need to repeat the work in 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.

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

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.

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 */
    }
};
怼怹恏 2024-08-06 00:45:31

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

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

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;
    }
};  
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文