C ++ : 복사 생성자 : Getters 또는 Access Member Vars를 직접 사용 하시겠습니까?

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

문제

사본 생성자가있는 간단한 컨테이너 클래스가 있습니다.

getters and setter를 사용하거나 멤버 변수에 직접 액세스하는 것이 좋습니다.

public Container 
{
   public:
   Container() {}

   Container(const Container& cont)          //option 1
   { 
       SetMyString(cont.GetMyString());
   }

   //OR

   Container(const Container& cont)          //option 2
   {
      m_str1 = cont.m_str1;
   }

   public string GetMyString() { return m_str1;}       

   public void SetMyString(string str) { m_str1 = str;}

   private:

   string m_str1;
}
  • 예에서는 모든 코드가 인라인이지만 실제 코드에는 인라인 코드가 없습니다.

업데이트 (29 9 월 9 일) :

이 답변 중 일부는 잘 쓰여졌지만이 질문의 요점을 놓치는 것 같습니다.

  • 이것은 Getters/Setters 대 변수를 사용하여 논의하는 간단한 예제입니다.

  • 이니셜 라이저 목록 또는 개인 유효성 검사기 기능은 실제로이 질문의 일부가 아닙니다. 두 디자인이 코드를 더 쉽게 유지 관리하고 확장 할 수 있는지 궁금합니다.

  • 일부 PPL 은이 예에서 문자열에 초점을 맞추고 있지만 예제 일뿐입니다. 대신 다른 객체라고 상상해보십시오.

  • 나는 성능에 대해 걱정하지 않습니다. 우리는 PDP-11에 대한 프로그래밍이 아닙니다

도움이 되었습니까?

해결책

문자열이 어떻게 반환되는지 예상합니까? 화이트 스페이스 트리밍, 널 확인 등? setMyString ()과 동일하게 대답이 예라면 Zillion 장소에서 코드를 변경할 필요가 없지만 그 getter 및 setter 메소드를 수정할 필요가 없기 때문에 액세스 방법이 더 좋습니다.

다른 팁

편집하다: 편집 된 질문에 답하기 :)

이것은 간단한 예제입니다 Getters/Setters vs를 사용하여 논의하십시오 변수

간단한 변수 모음이있는 경우 어떤 종류의 검증이 필요하지 않거나 추가 처리가 필요하지 않은 경우 대신 POD 사용을 고려할 수 있습니다. 에서 Stroustrup의 FAQ:

잘 설계된 클래스는 사용자에게 깨끗하고 간단한 인터페이스를 제공합니다. 표현을 숨기고 있습니다 그리고 사용자가 그 표현에 대해 알지 못하는 것을 저장합니다. 표현이 숨겨져 있지 않아야하는 경우 - 사용자는 원하는 방식으로 데이터 구성원을 변경할 수 있어야하므로 해당 클래스를 "평범한 오래된 데이터 구조"라고 생각할 수 있습니다.

요컨대, 이것은 Java가 아닙니다. 평범한 getters/setters는 변수를 노출시키는 것만 큼 나쁘기 때문에 작성해서는 안됩니다.

이니셜 라이저 목록 또는 개인 유효성 검사기 기능은 실제로이 질문의 일부가 아닙니다. 두 디자인이 코드를 더 쉽게 유지 관리하고 확장 할 수 있는지 궁금합니다.

다른 객체의 변수를 복사하는 경우 소스 객체는 유효한 상태 여야합니다. 아픈 형성된 소스 객체는 어떻게 처음부터 구성 되었습니까?! 생성자가 검증 작업을 수행하지 않아야합니까? 수정 멤버 기능이 입력을 검증하여 클래스 불변을 유지하는 책임이 있습니까? 사본 생성자에서 "유효한"객체를 검증하는 이유는 무엇입니까?

나는 성능에 대해 걱정하지 않습니다. 우리는 PDP-11에 대한 프로그래밍이 아닙니다

C ++에서 가장 우아한 코드는 일반적으로 최고의 성능 특성을 가지고 있지만 이것은 가장 우아한 스타일입니다.


당신은 an을 사용해야합니다 initializer list. 코드에서 m_str1 기본 구성입니다 그 다음에 새 값을 할당했습니다. 코드는 다음과 같은 것일 수 있습니다.

class Container 
{
public:
   Container() {}

   Container(const Container& cont) : m_str1(cont.m_str1)
   { }

   string GetMyString() { return m_str1;}       
   void SetMyString(string str) { m_str1 = str;}
private:
   string m_str1;
};

@cbrulak 당신은 IMO를 확인해서는 안됩니다 cont.m_str1 에서 copy constructor. 내가하는 일은 사물을 검증하는 것입니다 constructors. 유효성 검사 copy constructor 예를 들어 다음과 같은 첫 번째로 형성된 물체를 복사한다는 것을 의미합니다.

Container(const string& str) : m_str1(str)
{
    if(!valid(m_str1)) // valid() is a function to check your input
    {
        // throw an exception!
    }
}

이니셜 라이저 목록을 사용해야한다면 다음과 같이 질문이 의미가 없습니다.

Container(const Container& rhs)
  : m_str1(rhs.m_str1)
{}

Matthew Wilson 's에는 훌륭한 섹션이 있습니다 불완전한 C ++ 이는 멤버 이니셜 라이저 목록에 대한 모든 것을 설명하고 Const 및/또는 참조와 함께 코드를 더 안전하게 만드는 방법에 대해 설명합니다.

편집하다: 검증 및 const를 보여주는 예 :

class Container
{
public:
  Container(const string& str)
    : m_str1(validate_string(str))
  {}
private:
  static const string& validate_string(const string& str)
  {
    if(str.empty())
    {
      throw runtime_error("invalid argument");
    }
    return str;
  }
private:
  const string m_str1;
};

현재 입력 또는 출력의 자격이없는 상태로 작성된대로 Getter and Setter (원하는 경우 액세서 및 Mutator)는 전혀 아무것도 달성하지 못하므로 문자열을 공개하고 수행 할 수 있습니다.

실제 코드가 실제로 문자열에 적합한 경우, 당신이 다루고있는 것이 문자열이 전혀 없을 가능성이 높습니다. 대신, 그것은 단지 문자열처럼 보이는 것입니다. 이 경우에 실제로하고있는 것은 실제 유형이 문자열과 비슷한 경우 유형 시스템을 남용하는 것입니다. 그런 다음 실제 유형이 실제 문자열과 비교 한 모든 제한 사항을 시행하려는 세터를 제공합니다.

당신이 그 방향에서 그것을 보면, 대답은 상당히 분명해집니다. 문자열이 아니라, 스트링이 다른 (더 제한된) 유형처럼 행동하게하는 세터가있는 것은 대신해야 할 일이 당신이 정말로 원하는 입력. 해당 클래스를 올바르게 정의 한 결과, IT의 인스턴스를 공개합니다. (여기서 경우처럼 보이는 것처럼) 문자열로 시작하는 값을 할당하는 것이 합리적이면 해당 클래스에는 문자열을 인수로 취하는 할당 연산자가 포함되어야합니다. 경우 (여기에서도 경우에도 마찬가지로) 어떤 상황에서 해당 유형을 문자열로 변환하는 것이 합리적이므로 결과적으로 문자열을 생성하는 캐스트 연산자도 포함 할 수도 있습니다.

이것은 주변 클래스에서 세터와 getter를 사용하는 것보다 실질적인 개선을 제공합니다. 무엇보다도, 주변 클래스에 넣을 때 해당 클래스 내부의 코드가 Getter/Setter를 우회하여 세터가 시행해야 할 모든 것을 집행하는 것이 쉽습니다. 둘째, 정상적인 표기법을 유지합니다. Getter와 Setter를 사용하면 평범하고 읽기 어려운 코드를 작성해야합니다.

C ++에서 문자열 클래스의 주요 강점 중 하나는 연산자 과부하를 사용하여 다음과 같은 것을 교체 할 수 있습니다.

strcpy(strcat(filename, ".ext"));

와 함께:

filename += ".ext";

가독성을 향상시키기 위해. 그러나 그 줄이 우리가 게터와 세터를 통과하도록 강요하는 클래스의 일부라면 어떻게되는지 살펴보십시오.

some_object.setfilename(some_object.getfilename()+".ext");

무엇이든, C 코드는 실제로이 혼란보다 더 읽기 쉽습니다. 반면, 작업자 문자열과 연산자를 정의하는 클래스의 공개 대상으로 작업을 제대로 수행하면 어떻게되는지 고려하십시오. = :

some_object.filename += ".ext";

멋지고 단순하며 읽기 쉬운 것과 마찬가지로. 더 나은 점은 문자열에 대해 무언가를 시행해야한다면, 우리는 그 작은 클래스 만 검사 할 수 있다면, 우리는 실제로 하나 또는 두 개의 구체적인 잘 알려진 장소 (Operator =, 아마도 ctor 또는 2 개의 클래스)를 찾아야합니다. 세터를 사용하여 작업을 시도 할 때와는 완전히 다른 이야기입니다.

비용과 혜택이 무엇인지 스스로에게 물어보십시오.

비용 : 더 높은 런타임 오버 헤드. CTORS에서 가상 기능을 호출하는 것은 나쁜 생각이지만 세터와 게터는 가상이 아닐 것입니다.

혜택 : 세터/getter가 복잡한 일을한다면 코드를 반복하지 않습니다. 직관적이지 않은 일을한다면 그렇게하는 것을 잊지 않습니다.

비용/혜택 비율은 클래스마다 다릅니다. 그 비율이 확인되면 판단을 사용하십시오. 불변의 수업의 경우, 당신은 세터가없고, 당신은 getters가 필요하지 않습니다 (Const 회원과 참조는 아무도 변경/재 장착 할 수 없으므로 공개 될 수 있기 때문에).

사본 생성자를 작성하는 방법에는은 총알이 없습니다. 클래스에만 주이기 목록을 사용하여 상태를 공유하지 않는 인스턴스를 생성하는 사본 생성자를 제공하는 멤버 만있는 경우 좋은 방법입니다.

그렇지 않으면 실제로 생각해야합니다.

struct alpha {
   beta* m_beta;
   alpha() : m_beta(new beta()) {}
   ~alpha() { delete m_beta; }
   alpha(const alpha& a) {
     // need to copy? or do you have a shared state? copy on write?
     m_beta = new beta(*a.m_beta);
     // wrong
     m_beta = a.m_beta;
   }

사용하여 잠재적 인 segfault를 둘러 볼 수 있습니다. smart_ptr -하지만 결과적인 버그를 디버깅하는 재미를 많이 할 수 있습니다.

물론 그것은 더 재미있을 수 있습니다.

  • 주문형으로 생성 된 회원.
  • new beta(a.beta) ~이다 잘못된 어떻게 든 다형성을 소개하는 경우.

... 나사 그렇지 않으면 - 사본 생성자를 쓸 때 항상 생각하십시오.

왜 게스터와 세터가 필요합니까?

단순 :) - 그들은 불변의 보존 - 즉 "Mystring은 항상 짝수의 문자를 가지고있다"와 같은 수업을 보장합니다.

의도 한대로 구현 된 경우, 귀하의 객체는 항상 유효한 상태에 있으므로 회원 사본은 보증을 깨뜨릴 염려없이 회원을 직접 복사 할 수 있습니다. 다른 상태 검증을 통해 이미 검증 된 상태를 통과 할 수 있다는 이점은 없습니다.

Arak이 말했듯이, 가장 좋은 것은 이니셜 라이저 목록을 사용하는 것입니다.


그렇게 간단하지 않음 (1) :
Getters/Setters를 사용하는 또 다른 이유는 구현 세부 사항에 의존하지 않기 때문입니다. 그것은 사본 ctor에 대한 이상한 아이디어입니다. 그러한 구현 세부 사항을 변경할 때 거의 항상 CDA를 조정해야합니다.


그렇게 간단하지 않음 (2) :
나를 잘못 증명하기 위해 인스턴스 자체 또는 다른 외부 요인에 의존하는 불변량을 구성 할 수 있습니다. 하나의 (매우 반드시) 예 : "인스턴스 수가 짝수 인 경우 문자열 길이는 짝수입니다. 그렇지 않으면 홀수입니다." 이 경우 사본 CTOR는 문자열을 던지거나 조정해야합니다. 이 경우 세터/게터를 사용하는 데 도움이 될 수 있지만 일반 CAS는 아닙니다. 이상한 규칙에서 일반적인 규칙을 도출해서는 안됩니다.

검색된 방식을 변경하려는 경우 외부 클래스에 인터페이스를 사용하여 데이터에 액세스하는 것이 좋습니다. 그러나 클래스의 범위 내에 있고 복사 된 값의 내부 상태를 복제하려면 데이터 구성원과 직접 갈 것입니다.

말할 것도없이 Getter가 감소하지 않으면 몇 가지 기능 호출을 저장할 수 있습니다.

당신의 getters가 (인라인 및) 인 경우 virtual, 직접 회원 액세스를 사용하는 데 플러스 나 마이너스가 없다.

Getter가 가상이라면 거기에 있습니다 ~이다 오버 헤드 ... 그러나 그럼에도 불구하고 그것은 당신이 그들을 부르고 싶을 때입니다. 단지 서브 클래스로 무시할 경우를 대비하여!-)

많은 디자인 질문에 적합한 간단한 테스트가 있습니다. 여기에는 부작용을 추가하고 어떤 파손이 있는지 확인하십시오.

Setter가 값을 할당 할뿐만 아니라 감사 레코드를 작성하거나 메시지를 기록하거나 이벤트를 제기한다고 가정합니다. 객체를 복사 할 때 모든 속성에 대해 이런 일이 발생 하시겠습니까? 아마도 - 생성자에서 세터를 호출하는 것은 논리적으로 잘못된 것입니다 (세터가 실제로 할당이있는 경우에도).

샘플에 많은 엔트리 레벨 C ++ "No-No 's"가 있다는 다른 포스터에 동의하지만,이를 측면에 넣고 직접 질문에 대답합니다.

실제로, 나는 모든 멤버 필드*를 공개하지는 않지만 필요할 때 가져 오기/설정으로 옮기는 경향이 있습니다.

이제 나는 이것이 반드시 권장되는 연습 일 필요는 없다고 말할 것이며, 많은 실무자들은 이것을 혐오하고 모든 필드에 세터/getter가 있어야한다고 말할 것입니다.

아마도. 그러나 나는 실제로 이것이 항상 필요하지는 않다는 것을 알았습니다. 물론, 그것은 나중에 분야를 대중에서 게터로 바꿀 때 고통을 유발하며, 때로는 수업이 어떤 사용법을 가지고 있는지 알 때, 나는 그것을 설정/얻고 처음부터 현장을 보호하거나 비공개로 만듭니다.

ymmv

RF

  • 필드를 "변수"라고 부릅니다. - 기능/메소드 내의 로컬 변수에 대해서만 해당 용어를 사용하는 것이 좋습니다.
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top