program story

언제 복사 생성자를 사용해야합니까?

inputbox 2020. 10. 12. 07:31
반응형

언제 복사 생성자를 사용해야합니까?


C ++ 컴파일러가 클래스에 대한 복사 생성자를 생성한다는 것을 알고 있습니다. 어떤 경우에 사용자 정의 복사 생성자를 작성해야합니까? 몇 가지 예를 들어 줄 수 있습니까?


컴파일러에 의해 생성 된 복사 생성자는 멤버 단위 복사를 수행합니다. 때로는 충분하지 않습니다. 예를 들면 :

class Class {
public:
    Class( const char* str );
    ~Class();
private:
    char* stored;
};

Class::Class( const char* str )
{
    stored = new char[srtlen( str ) + 1 ];
    strcpy( stored, str );
}

Class::~Class()
{
    delete[] stored;
}

이 경우 멤버의 stored멤버 별 복사 는 버퍼를 복제하지 않습니다 (포인터 만 복사됩니다). 따라서 버퍼를 공유하는 첫 번째 삭제 된 복사본은 delete[]성공적으로 호출 되고 두 번째는 정의되지 않은 동작으로 실행됩니다. 깊은 복사 복사 생성자 (및 할당 연산자)가 필요합니다.

Class::Class( const Class& another )
{
    stored = new char[strlen(another.stored) + 1];
    strcpy( stored, another.stored );
}

void Class::operator = ( const Class& another )
{
    char* temp = new char[strlen(another.stored) + 1];
    strcpy( temp, another.stored);
    delete[] stored;
    stored = temp;
}

나는의 규칙이 Rule of Five인용되지 않았다는 것에 약간 오싹합니다 .

이 규칙은 매우 간단합니다.

다섯 가지 규칙 :
소멸자, 복사 생성자, 복사 할당 연산자, 이동 생성자 또는 이동 할당 연산자 중 하나를 작성할 때마다 다른 4 개를 작성해야합니다.

하지만 따라야 할보다 일반적인 지침이 있으며, 예외 안전 코드를 작성해야 할 필요성에서 비롯됩니다.

각 리소스는 전용 개체에서 관리해야합니다.

여기 @sharptooth의 코드는 여전히 (대부분) 괜찮지 만, 그의 클래스에 두 번째 속성을 추가한다면 그렇지 않을 것입니다. 다음 클래스를 고려하십시오.

class Erroneous
{
public:
  Erroneous();
  // ... others
private:
  Foo* mFoo;
  Bar* mBar;
};

Erroneous::Erroneous(): mFoo(new Foo()), mBar(new Bar()) {}

하면 어떻게됩니까 new Bar던져? 가 가리키는 개체를 어떻게 삭제 mFoo합니까? 솔루션 (기능 수준 try / catch ...)이 있지만 확장되지 않습니다.

상황을 처리하는 적절한 방법은 원시 포인터 대신 적절한 클래스를 사용하는 것입니다.

class Righteous
{
public:
private:
  std::unique_ptr<Foo> mFoo;
  std::unique_ptr<Bar> mBar;
};

동일한 생성자 구현 (또는 실제로 사용 make_unique)으로 이제 예외 안전을 무료로 사용할 수 있습니다 !!! 흥미롭지 않나요? 그리고 무엇보다도 적절한 소멸자에 대해 더 이상 걱정할 필요가 없습니다! 나는 내 자신의 작성해야합니까 Copy Constructor하고 Assignment Operator있기 때문에,하지만 unique_ptr이러한 작업을 정의하지 않습니다 ...하지만 여기에 문제가되지 않습니다)

따라서 sharptooth의 수업이 재검토되었습니다.

class Class
{
public:
  Class(char const* str): mData(str) {}
private:
  std::string mData;
};

나는 당신에 대해 모르지만 내 것이 더 쉽다고 생각합니다.)


나는 내 연습을 떠올려 복사 생성자를 명시 적으로 선언 / 정의해야 할 때 다음과 같은 경우를 생각할 수 있습니다. 사례를 두 가지 범주로 분류했습니다.

  • 정확성 / 의미 -사용자 정의 복사 생성자를 제공하지 않으면 해당 유형을 사용하는 프로그램이 컴파일되지 않거나 잘못 작동 할 수 있습니다.
  • 최적화 -컴파일러 생성 복사 생성자에 대한 좋은 대안을 제공하면 프로그램을 더 빠르게 만들 수 있습니다.


정확성 / 의미

이 섹션에서는 해당 유형을 사용하는 프로그램의 올바른 작동을 위해 복사 생성자를 선언 / 정의해야하는 경우를 설명합니다.

이 섹션을 읽은 후 컴파일러가 자체적으로 복사 생성자를 생성하도록 허용하는 몇 가지 함정에 대해 배웁니다. 따라서 seand 가 그의 답변 에서 언급했듯이 새 클래스에 대한 복사 가능성을 끄고 나중에 실제로 필요할 때 의도적으로 활성화하는 것이 항상 안전 합니다.

C ++ 03에서 클래스를 복사 할 수 없도록 만드는 방법

private copy-constructor를 선언하고 이에 대한 구현을 제공하지 마십시오 (그러면 해당 유형의 개체가 클래스의 자체 범위 또는 해당 친구에 의해 복사 되더라도 빌드가 연결 단계에서 실패 함).

C ++ 11 이상에서 클래스를 복사 할 수 없도록 만드는 방법

=delete끝에 복사 생성자를 선언하십시오 .


얕은 대 전체 복사

이것은 가장 잘 이해 된 사례이며 실제로 다른 답변에서 언급 된 유일한 사례입니다. shaprtooth그것을 꽤 잘 덮었 습니다. 객체가 독점적으로 소유해야하는 심층 복사 리소스를 모든 유형의 리소스에 적용 할 수 있다는 점을 추가하고 싶습니다. 동적 할당 메모리는 한 종류에 불과합니다. 필요한 경우 객체를 깊게 복사하려면

  • 디스크에 임시 파일 복사
  • 별도의 네트워크 연결 열기
  • 별도의 작업자 스레드 생성
  • 별도의 OpenGL 프레임 버퍼 할당
  • 기타

자체 등록 개체

모든 객체가 생성 된 방식에 관계없이 어떻게 든 등록되어야하는 클래스를 고려하십시오. 몇 가지 예 :

  • 가장 간단한 예는 현재 존재하는 개체의 총 개수를 유지하는 것입니다. 개체 등록은 정적 카운터를 증가시키는 것입니다.

  • 더 복잡한 예는 해당 유형의 모든 기존 개체에 대한 참조가 저장되는 단일 레지스트리를 갖는 것입니다 (알림이 모든 개체에 전달 될 수 있음).

  • 참조 카운트 스마트 포인터는이 범주에서 특별한 경우로 간주 될 수 있습니다. 새 포인터는 전역 레지스트리가 아닌 공유 리소스에 자신을 "등록"합니다.

이러한 자체 등록 작업은 유형의 모든 생성자에 의해 수행되어야하며 복사 생성자도 예외는 아닙니다.


내부 상호 참조가있는 개체

일부 객체는 서로 다른 하위 객체간에 직접적인 상호 참조가있는 사소하지 않은 내부 구조를 가질 수 있습니다 (사실 이러한 내부 상호 참조는이 경우를 트리거하기에 충분합니다). 컴파일러에서 제공하는 복사 생성자는 내부 개체 내 연결을 끊고 개체 연결로 변환합니다 .

예 :

struct MarriedMan;
struct MarriedWoman;

struct MarriedMan {
    // ...
    MarriedWoman* wife;   // association
};

struct MarriedWoman {
    // ...
    MarriedMan* husband;  // association
};

struct MarriedCouple {
    MarriedWoman wife;    // aggregation
    MarriedMan   husband; // aggregation

    MarriedCouple() {
        wife.husband = &husband;
        husband.wife = &wife;
    }
};

MarriedCouple couple1; // couple1.wife and couple1.husband are spouses

MarriedCouple couple2(couple1);
// Are couple2.wife and couple2.husband indeed spouses?
// Why does couple2.wife say that she is married to couple1.husband?
// Why does couple2.husband say that he is married to couple1.wife?

특정 기준을 충족하는 개체 만 복사 할 수 있습니다.

어떤 상태 (예 : default-constructed-state)에있는 동안 객체를 복사 해도 안전하고 그렇지 않은 경우 복사 해도 안전 하지 않은 클래스가있을 수 있습니다 . 안전한 복사 객체 복사를 허용하려면 방어 적으로 프로그래밍하는 경우 사용자 정의 복사 생성자에서 런타임 검사가 필요합니다.


복사 할 수없는 하위 개체

때로는 복사 가능해야하는 클래스가 복사 불가능한 하위 개체를 집계합니다. 일반적으로 이것은 관찰 할 수없는 상태의 객체에서 발생합니다 (이 경우는 아래의 "최적화"섹션에서 자세히 설명합니다). 컴파일러는이 경우를 인식하는 데 도움이됩니다.


유사 복사 가능한 하위 개체

복사 가능해야하는 클래스는 유사 복사 가능 유형의 하위 개체를 집계 할 수 있습니다. 유사 복사 가능 형식은 엄격한 의미에서 복사 생성자를 제공하지 않지만 개체의 개념적 복사본을 만들 수있는 또 다른 생성자를 가지고 있습니다. 유형을 유사 복사 가능하게 만드는 이유는 유형의 복사 의미론에 대한 완전한 합의가 없을 때입니다.

예를 들어, 객체 자체 등록 사례를 다시 살펴보면 객체가 완전한 독립형 객체 인 경우에만 전역 객체 관리자에 등록해야하는 상황이있을 수 있습니다. 다른 개체의 하위 개체 인 경우 관리 책임은 포함 개체에 있습니다.

또는 얕은 복사와 전체 복사가 모두 지원되어야합니다 (둘 중 어느 것도 기본값이 아님).

Then the final decision is left to the users of that type - when copying objects, they must explicitly specify (through additional arguments) the intended method of copying.

In case of a non-defensive approach to programming, it is also possible that both a regular copy-constructor and a quasi-copy-constructor are present. This can be justified when in the vast majority of cases a single copying method should be applied, while in rare but well understood situations alternative copying methods should be used. Then the compiler won't complain that it is unable to implicitly define the copy constructor; it will be the users' sole responsibility to remember and check whether a sub-object of that type should be copied via a quasi-copy-constructor.


Don't copy state that is strongly associated with the object's identity

In rare cases a subset of the object's observable state may constitute (or be considered) an inseparable part of the object's identity and should not be transferable to other objects (though this can be somewhat controversial).

Examples:

  • The UID of the object (but this one also belongs to the "self-registration" case from above, since the id must be obtained in an act of self-registration).

  • History of the object (e.g. the Undo/Redo stack) in the case when the new object must not inherit the history of the source object, but instead start with a single history item "Copied at <TIME> from <OTHER_OBJECT_ID>".

In such cases the copy constructor must skip copying the corresponding sub-objects.


Enforcing correct signature of the copy constructor

The signature of the compiler-provided copy constructor depends on what copy constructors are available for the sub-objects. If at least one sub-object doesn't have a real copy constructor (taking the source object by constant reference) but instead has a mutating copy-constructor (taking the source object by non-constant reference) then the compiler will have no choice but to implicitly declare and then define a mutating copy-constructor.

Now, what if the "mutating" copy-constructor of the sub-object's type doesn't actually mutate the source object (and was simply written by a programmer who doesn't know about the const keyword)? If we can't have that code fixed by adding the missing const, then the other option is to declare our own user-defined copy constructor with a correct signature and commit the sin of turning to a const_cast.


Copy-on-write (COW)

A COW container that has given away direct references to its internal data MUST be deep-copied at the time of construction, otherwise it may behave as a reference counting handle.

Though COW is an optimization technique, this logic in the copy constructor is crucial for its correct implementation. That is why I placed this case here rather than in the "Optimization" section, where we go next.



Optimization

In the following cases you may want/need to define your own copy constructor out of optimization concerns:


Structure optimization during copy

Consider a container that supports element removal operations, but may do so by simply marking the removed element as deleted, and recycle its slot later. When a copy of such a container is made, it may make sense to compact the surviving data rather than preserve the "deleted" slots as is.


Skip copying non-observable state

An object may contain data that is not part of its observable state. Usually, this is cached/memoized data accumulated over the object's lifetime in order to speed-up certain slow query operations performed by the object. It is safe to skip copying that data since it will be recalculated when (and if!) the relevant operations are performed. Copying this data may be unjustified, as it may be quickly invalidated if the object's observable state (from which the cached data is derived) is modified by mutating operations (and if we are not going to modify the object, why are we creating a deep copy then?)

This optimization is justified only if the auxiliary data is large compared to the data representing the observable state.


Disable implicit copying

C++ allows to disable implicit copying by declaring the copy constructor explicit. Then objects of that class cannot be passed into functions and/or returned from functions by value. This trick can be used for a type that appears to be lightweight but is indeed very expensive to copy (though, making it quasi-copyable might be a better choice).

In C++03 declaring a copy constructor required defining it too (of course, if you intended to use it). Hence, going for such a copy constructor merely out of the concern being discussed meant that you had to write the same code that the compiler would automatically generate for you.

C++11 and newer standards allow declaring special member functions (the default and copy constructors, the copy-assignment operator, and the destructor) with an explicit request to use the default implementation (just end the declaration with =default).



TODOs

This answer can be improved as follows:

  • Add more example code
  • Illustrate the "Objects with internal cross-references" case
  • Add some links

If you have a class that has dynamically allocated content. For example you store the title of a book as a char * and set the title with new, copy will not work.

You would have to write a copy constructor that does title = new char[length+1] and then strcpy(title, titleIn). The copy constructor would just do a "shallow" copy.


Copy Constructor is called when an object is either passed by value, returned by value, or explicitly copied. If there is no copy constructor, c++ creates a default copy constructor which makes a shallow copy. If the object has no pointers to dynamically allocated memory then shallow copy will do.


It's often a good idea to disable copy ctor, and operator= unless the class specifically needs it. This may prevent inefficiencies such as passing an arg by value when reference is intended. Also the compiler generated methods may be invalid.


Let's consider below code snippet:

class base{
    int a, *p;
public:
    base(){
        p = new int;
    }
    void SetData(int, int);
    void ShowData();
    base(const base& old_ref){
        //No coding present.
    }
};
void base :: ShowData(){
    cout<<this->a<<" "<<*(this->p)<<endl;
}
void base :: SetData(int a, int b){
    this->a = a;
    *(this->p) = b;
}
int main(void)
{
    base b1;
    b1.SetData(2, 3);
    b1.ShowData();
    base b2 = b1; //!! Copy constructor called.
    b2.ShowData();
    return 0;
}

Output: 
2 3 //b1.ShowData();
1996774332 1205913761 //b2.ShowData();

b2.ShowData(); gives junk output because there is a user-defined copy-constructor created with no code written to copy data explicitly. So compiler does not create the same.

Just thought of sharing this knowledge with you all, although most of you know it already.

Cheers... Happy coding!!!

참고URL : https://stackoverflow.com/questions/3278625/when-do-we-have-to-use-copy-constructors

반응형