5의 규칙-사용 여부?


20

3 규칙 ( 새로운 c ++ 표준에서 5 규칙 )은 다음과 같습니다.

소멸자, 복사 생성자 또는 복사 할당 연산자를 명시 적으로 선언해야하는 경우 세 개 모두 명시 적으로 선언해야합니다.

그러나 Martin의 " Clean Code "는 빈 생성자와 소멸자를 모두 제거하도록 조언합니다 (293 페이지, G12 : Clutter ).

구현이없는 기본 생성자는 어떤 용도로 사용됩니까? 의미없는 아티팩트로 코드를 복잡하게 만드는 것입니다.

그렇다면이 두 가지 반대 의견을 처리하는 방법은 무엇입니까? 빈 생성자 / 소멸자가 실제로 구현되어야합니까?


다음 예제는 내가 의미하는 바를 정확하게 보여줍니다.

#include <iostream>
#include <memory>

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    ~A(){}
    A( const A & other ) : v( new int( *other.v ) ) {}
    A& operator=( const A & other )
    {
        v.reset( new int( *other.v ) );
        return *this;
    }

    std::auto_ptr< int > v;
};
int main()
{
    const A a( 55 );
    std::cout<< "a value = " << *a.v << std::endl;
    A b(a);
    std::cout<< "b value = " << *b.v << std::endl;
    const A c(11);
    std::cout<< "c value = " << *c.v << std::endl;
    b = c;
    std::cout<< "b new value = " << *b.v << std::endl;
}

g ++ 4.6.1을 사용하여 다음과 같이 잘 컴파일합니다.

g++ -std=c++0x -Wall -Wextra -pedantic example.cpp

에 대한 소멸자 struct A는 비어 있으며 실제로 필요하지 않습니다. 그렇다면, 아니면 제거해야합니까?


15
2 인용문은 다른 것들에 대해 이야기합니다. 아니면 당신의 요점을 완전히 그리워합니다.
Benjamin Bannier

1
@honk 우리 팀의 코딩 표준에는 항상 4 개 (생성자, 소멸자, 복사 생성자)를 모두 선언하는 규칙이 있습니다. 나는 그것이 정말로 의미가 있는지 궁금했다. 소멸자가 비어 있어도 항상 선언해야합니까?
BЈовић

빈 desctructors에 대해서는 codesynthesis.com/~boris/blog/2012/04/04/…에 대해 생각 하십시오 . 그렇지 않으면 3 (5)의 규칙이 나에게 완벽한 의미가 있습니다, 아무 생각 왜 사람은 4의 규칙 싶지 않을 것이다
벤자민 Bannier

@honk 인터넷에서 찾은 정보에 대해 살펴보십시오. 모든 것이 사실은 아닙니다. 예를 들어, virtual ~base () = default;컴파일하지 않습니다 (타당한 이유가 있습니다)
BЈовић

@VJovic, 아니오 가상 소멸자가 아닌 한 빈 소멸자를 선언하지 않아도됩니다. 그리고 우리가 주제에있는 동안, 당신은 auto_ptr어느 쪽 도 사용해서는 안됩니다 .
Dima

답변:


44

처음에는 규칙에 "아마도"라고 표시되므로 항상 적용되는 것은 아닙니다.

두 번째 요점은 세 가지 중 하나를 선언해야하는 경우 메모리 할당과 같은 특별한 작업을 수행하기 때문입니다. 이 경우 다른 작업은 동일한 작업 (예 : 복사 생성자에서 동적으로 할당 된 메모리의 내용을 복사하거나 이러한 메모리를 비우는 등)을 처리해야하므로 비어 있지 않습니다.

결론적으로 빈 생성자 또는 소멸자를 선언해서는 안되지만, 필요한 경우 다른 생성자도 필요할 가능성이 큽니다.

예를 들면 다음과 같습니다. 이러한 경우 소멸자를 내버려 둘 수 있습니다. 분명히 아무것도하지 않습니다. 스마트 포인터의 사용법은 3의 규칙이 적용되지 않는 위치와 이유에 대한 완벽한 예입니다.

그것은 당신이 놓칠 수있는 중요한 기능을 구현하는 것을 잊었을 경우를 대비하여 코드를 다시 살펴볼 곳을위한 안내서 일뿐입니다.


스마트 포인터를 사용하면 대부분의 경우 소멸자가 비어 있습니다 (거의 모든 클래스가 pimpl 관용구를 사용하기 때문에 코드베이스에서 소멸자의> 99 %가 비어 있음).
BЈовић

와우, 나는 그것을 냄새 나는 것으로 부르는 너무나 놀랍습니다. 많은 컴파일러에서 pimpled는 최적화하기가 더 어려울 것입니다 (예 : 인라인하기가 더 어렵습니다).
Benjamin Bannier

@honk "많은 컴파일러 pimpled"는 무엇을 의미합니까? :)
BЈовић

@VJovic : 죄송합니다, 오타 '여드름 투성이의 코드'
벤자민 Bannier

4

여기에는 모순이 없습니다. 3의 규칙은 소멸자, 사본 생성자 및 사본 할당 연산자에 대해 이야기합니다. Bob 아저씨는 빈 기본 생성자에 대해 이야기합니다.

소멸자가 필요한 경우 클래스에 동적으로 할당 된 메모리에 대한 포인터가 포함되어있을 수 있으며 복사 ctor와 operator=()딥 카피를 수행 할 수 있습니다. 이것은 기본 생성자가 필요한지 여부와 완전히 직교합니다.

또한 C ++에서는 기본 생성자가 비어 있어도 기본 생성자가 필요한 상황이 있습니다. 클래스에 기본이 아닌 생성자가 있다고 가정 해 봅시다. 이 경우 컴파일러는 기본 생성자를 생성하지 않습니다. 즉,이 클래스의 오브젝트는 STL 컨테이너에 저장할 수 없습니다. 컨테이너는 오브젝트가 기본 구성 가능할 것으로 예상하기 때문입니다.

반면에 클래스의 객체를 STL 컨테이너에 넣지 않으려는 경우 빈 기본 생성자는 확실히 쓸모없는 혼란입니다.


2

여기에 기본 생성자 / 할당 / 소멸자에 해당하는 기본 (*)에 해당하는 목적이 있습니다. 문제에 대한 사실을 기록하고 기본 동작이 올바른지 확인하십시오. BTW는 C ++ 11에서 =default그 목적을 달성 할 수 있을지 알기에 충분히 안정화되지 않았습니다 .

(또 다른 잠재적 인 목적이 있습니다. 기본 인라인 대신 라인 외부 정의를 제공하십시오. 이유가있을 경우 명시 적으로 문서화하는 것이 좋습니다).

(*) 세 가지 규칙이 적용되지 않은 실제 사례를 기억하지 못하기 때문에 하나의 일을 해야하는 경우 다른 사람의 일을해야했습니다.


예제를 추가 한 후 편집하십시오. auto_ptr을 사용한 예제가 흥미 롭습니다. 스마트 포인터를 사용하고 있지만 업무에 적합한 것은 아닙니다. 차라리 당신이 한 일보다 (특히 상황이 자주 발생하는 경우) 쓰는 것이 좋습니다. (실수하지 않으면 표준도 부스트도 제공하지 않습니다).


이 예는 내 요점을 보여줍니다. 소멸자는 실제로 필요하지 않지만 3의 규칙은 그것이 있어야한다고 말합니다.
BЈовић

1

5의 규칙은 3의 규칙의 cautalative 확장입니다. 이것은 cautelative 행동으로 다시 개체 오용이 발생할 수 있습니다.

소멸자가 필요하면 기본값 이외의 "자원 관리"를 수행했음을 의미합니다 ( 값을 구성하고 소멸하십시오 ).

복사, 할당, 이동 및 전송은 기본 복사 으로 수행되므로 값만 보유하지 않으면 수행 할 작업을 정의해야합니다.

즉, C ++은 이동을 정의하면 복사를 삭제하고 복사를 정의하면 이동을 삭제합니다. 대부분의 경우 값을 에뮬레이션할지 (따라서 복사 뮤트가 리소스를 복제하고 이동이 의미가 없는지) 또는 리소스 관리자 (따라서 복사가 의미가없는 경우 리소스를 이동하는지)를 정의해야합니다. 규칙 (3)의 지배하게 다른 3 )

복사와 이동 (5의 규칙)을 모두 정의해야하는 경우는 매우 드물다. 일반적으로 "큰 값"은 별개의 객체에 부여 된 경우 복사해야하지만 임시 객체에서 가져온 경우 이동할 수 있습니다 (피할 수 없음) 복제는 파괴 ). STL 컨테이너 또는 산술 컨테이너의 경우입니다.

그들은 때문에 그들이지지 복사해야 례는 행렬 일 수 있다 , 값 ( a=b; c=b; a*=2; b*=3;서로 영향을주지한다)하지만,이 (또한 지원하는 이동시킴으로써 최적화 될 수 a = 3*b+4*c있다 +두 임시직을 얻어 임시 생성한다 : 피 복제, 삭제를 할 수있다 유능한)


1

나는 "합리적인 클래스가 비어있는 가상 소멸자를 제외하고 소멸자를 필요로하는 경우 아마도 복사 생성자와 할당 연산자를 필요로한다"는 세 가지 규칙에 대한 다른 표현을 선호한다.

소멸자로부터 단방향 관계로 지정하면 몇 가지 사항이 더 명확 해집니다.

  1. 기본이 아닌 복사 생성자 또는 할당 연산자를 최적화로만 제공하는 경우에는 적용되지 않습니다.

  2. 규칙의 이유는 기본 복사 생성자 또는 할당 연산자가 수동 리소스 관리를 망칠 수 있기 때문입니다. 리소스를 수동으로 관리하는 경우 리소스를 해제하려면 소멸자가 필요하다는 것을 알았을 것입니다.


-3

토론에서 언급되지 않은 또 다른 요점이 있습니다. 소멸자는 항상 가상이어야합니다.

struct A
{
    A( const int value ) : v( new int( value ) ) {}
    virtual ~A(){}
    ...
}

생성자는 모든 파생 클래스에서도 가상으로 만들려면 기본 클래스에서 가상으로 선언해야합니다. 따라서 기본 클래스에 소멸자가 필요하지 않더라도 빈 소멸자를 선언하고 구현합니다.

(-Wall -Wextra -Weffc ++)에 모든 경고를 표시하면 g ++에서 이에 대해 경고합니다. 클래스가 결국 기본 클래스가 될지 알 수 없기 때문에 항상 모든 클래스에서 가상 소멸자를 선언하는 것이 좋습니다. 가상 소멸자가 필요하지 않으면 해를 끼치 지 않습니다. 그렇다면 오류를 찾기 위해 시간을 절약하십시오.


1
그러나 나는 가상 생성자를 원하지 않습니다. 그렇게하면 모든 메소드를 호출 할 때마다 가상 디스패치가 사용됩니다. btw는 C ++에 "가상 생성자"와 같은 것은 없다는 점에 유의하십시오. 또한 예제를 매우 높은 경고 수준으로 컴파일했습니다.
BЈовић

gcc가 경고에 사용하는 규칙 인 IIRC와 내가 일반적으로 따르는 규칙은 클래스에 다른 가상 메소드가있는 경우 가상 소멸자가 있어야한다는 것입니다.
Jules
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.