2009-08-04 4 views
73

나는 한 세트의 생성자와 대입 연산자로 클래스 B를 가졌다.C++에서 기본 클래스의 생성자와 대입 연산자를 사용하는 방법은 무엇입니까?

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 
} 

난 그냥 함수 foo는 (우선합니다 상속 클래스 D)를 만들려면, 그리고 다른 변화는 필요하지 않습니다.

하지만 D가 B로 복사 생성자와 대입 연산자를 포함 생성자의 동일한 세트, 갖고 싶어 :

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

내가 D에 그들 모두를 다시 작성해야하거나, 수행을 사용하는 방법이 B의 생성자와 연산자? 특히 B의 private 멤버 변수에 모두 액세스해야하기 때문에 대입 연산자를 다시 작성하지 않아야합니다.

+0

, 당신은 B :: 연산자를 사용하여 '사용할 수 =;'할당 연산자를 상속하지만, 복사 및 이동 생성자는 상속 될 수 없습니다하는 방법 : https://stackoverflow.com/q/5,447,906분의 49,045,026 –

답변

93

당신은 명시 적으로 생성자와 할당 연산자를 호출 할 수 있습니다. 복사 할당은 자원과 복사 생성자 돈을 해제해야하기 때문에,

일반적으로
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 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

이것은 무엇을 의미합니까? 'Base (const base &)' – qed

+1

@CravingSpirit [복사 생성자] (http://en.wikipedia.org/wiki/Copy_constructor)입니다 (인수 이름 생략). – Motti

+0

감사. 이미 operator = overloading이있는 경우 복사 생성자가 필요한 이유는 무엇입니까? – qed

16

짧은 답변 : 당신은 D에서 작업을 반복해야합니다 예

긴 대답 :

파생 클래스 'D'는 컴파일러에 의해 생성 한 후 새로운 멤버 변수 기본 버전 (포함하지 않는 경우해야 잘 작동). 기본 Copy 생성자는 부모 복사 생성자를 호출하고 기본 할당 연산자는 부모 할당 연산자를 호출합니다.

하지만 '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 */ 
    } 
}; 
+0

+1. 이제부터 스왑 멤버 메소드에 위임하는 유형에 대해 std :: swap에 특수화를 추가하십시오. 'namespace std {template <> void std :: swap (D & lhs, D & rhs) {lhs.swap (rhs); }} '이렇게하면 특수 스왑 연산을 STL 알고리즘에서 사용할 수 있습니다. –

+1

X *와 같은 네임 스페이스에 자유 스왑 함수를 추가하면 (ADL을 통해) 동일한 효과를 가져야하지만 누군가 MSVC가 std :: swap을 명시 적으로 잘못 호출하여 dribeas가 올바르게 작동한다고 말한 적이 있습니다 ... –

+0

기술적으로 표준 네임 스페이스에 항목을 추가 할 수 없습니다. –

4

당신이 가장 가능성이 당신의 디자인 (: 슬라이스, 엔티티 의미값 의미 대 힌트를) 결함이있다. 다형성 계층 구조의 객체에 전체 복사/값 의미을 갖는 것이 종종 필요하지 않습니다. 나중에 필요할 경우를 대비하여 제공하려는 경우, 필요가 없다는 의미입니다.(부스트 :: 예를 들어 noncopyable에서 상속에 의해) 대신 기본 클래스가 아닌 복사 가능한을 확인하고 그게 다야.

유일한 올바른 솔루션은 필요 정말 나타난다는 봉투 문자 관용구, 또는 일반 객체 숀 부모와 알렉산더 스테파노 프 IIRC에 의해의 기사에서 작은 프레임 워크 때. 다른 모든 솔루션은 슬라이싱 및/또는 LSP에 문제를 일으킬 수 있습니다.

1

당신은 기본 또는 생성자를 복사 할 수있는 모든 생성자를 재정의해야합니다. SBI가 언급 한 바와 같이, 당신은 어떤 생성자를 정의하는 경우, 해당

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

참고 : 당신은 모든 기지의 버전을 호출합니다 (표준에 따라) 컴파일러가 제공하는 것과 같은 복사 생성자도 할당 연산자를 재정의 할 필요가 없습니다 컴파일러는 기본 생성자를 생성하지 않으며 복사 생성자를 포함합니다.

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

흥미로운 것은이 명시 적으로이 함수를 정의하지 않은 경우에도 작동한다는 것입니다 (이것은 다음 컴파일러 생성 함수를 사용) :

+0

다른 ctor (사본 ctor 포함)가 정의되어 있으면 컴파일러에서 기본 ctor를 제공하지 않습니다. 따라서'derived '가 기본 ctor를 갖기를 원하면 명시 적으로 정의해야합니다. – sbi

1

원래 코드가 잘못되었습니다 't !!!

것은이를 이해하기 위해 고려 : 다른 생성자를 복사 할당을 복사, 그래서

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

:

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(const B& b){(*this) = b;} // copy constructor 

는 당신이 unexisting 메모리 삭제됩니다. 당신은 단지`foo` 메소드를 오버라이드 (override) 할 경우

관련 문제