C ++에서 기본 클래스의 생성자 및 할당 연산자를 사용하는 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/1226634

문제

수업이 있습니다 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, 또는 사용 방법이 있습니까? B의 생성자와 운영자? 특히 할당 연산자를 다시 쓰는 것을 피하고 싶습니다. B개인 회원 변수.

도움이 되었습니까?

해결책

생성자 및 과제 연산자를 명시 적으로 호출 할 수 있습니다.

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

다른 팁

짧은 답변 : 예 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 */
    }
};

당신은 아마도 당신의 디자인에 결함이있을 것입니다 (힌트 : 슬라이스, 엔티티 의미론 vs 가치 의미론). 전체 사본/가치 의미론 다형성 계층 구조의 물체에서 종종 필요하지 않습니다. 나중에 필요할 수있는 경우를 대비하여 제공하고 싶다면 필요하지 않다는 의미입니다. 기본 클래스를 대신 복사 할 수 없게 만드십시오 (예를 들어 Boost :: Copyable에서 상속함으로써). 그리고 그게 전부입니다.

필요할 때 유일한 올바른 솔루션 진짜 나타납니다 봉투 글린 관용구, 또는 기사의 작은 프레임 워크 일반 대상 Sean Parent와 Alexander Stepanov IIRC. 다른 모든 솔루션은 슬라이스 및/또는 LSP에 문제가됩니다.

주제에 대해서는 C ++ CoreReference C.67도 참조하십시오. c.67 : 기본 클래스는 복사를 억제하고 "복사"가 원하는 경우 대신 가상 클론을 제공해야합니다..

당신은 그렇지 않은 모든 생성자를 재정의해야합니다. 기본 또는 복사 생성자. 컴파일러가 제공 한 것 (표준에 따라)이 모든베이스의 버전을 호출 할 때 카피 생성자 나 할당 연산자를 재정의 할 필요는 없습니다.

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가 언급 한 바와 같이, 생성자를 정의하면 컴파일러는 귀하의 기본 생성자를 생성하지 않으며 사본 생성자가 포함됩니다.

원래 코드가 잘못되었습니다.

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

당신은 존재하지 않는 메모리를 삭제합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top