2012-06-10 2 views
0

식기 세척기 프로그램을 작성하고 있는데 식기 세척기에는 펌프, 모터 및 ID가 있습니다. 펌프, 모터, 날짜, 시간은 식기 세척기가 사용할 다른 작은 클래스입니다. 디버거로 확인했지만 식기 세척기 클래스를 만들 때 원하는 값이 초기화되지 않았습니다. 나는 내가 뭔가 잘못하고 있다고 생각하지만 뭐라구? :(C++ 초기화 목록, 클래스 (집계) 클래스

는 그래서 식기 세척기 클래스는 다음과 같습니다 :

Dishwasher siemens( 
     Pump(160, "011219991143"), 
     Motor(1300, "081220031201"), 
     "010720081032", 
     17.5); 

:

Dishwasher::Dishwasher(Pump p, Motor m, char *str, float f) 
    : pump(p), motor(m), max_load(f) 
{ 
    washer_id = new char [(strlen(str)+1)]; 
    strcpy (washer_id,str); 
    time_built.id2time(washer_id); 
    date_built.id2date(washer_id); 
} 

이 내가 클래스를 만드는 방법은 다음과 같습니다

class Dishwasher { 
    Pump pump; // the pump inside the dishwasher 
    Motor motor;// the motor inside the dishwasher 

    char* washer_id;//011220001032 means first of December 2000 at 10:32h 
    Time time_built;// Time variable, when the Dishwasher was built 
    Date date_built;// Date variable, when the Dishwasher was built 

    Time washing_time;  // a time object, like 1:15 h 
public: 
    Dishwasher(Pump, Motor, char*, float); 
}; 

이 내가 클래스를 초기화하는 방법입니다 더 나은 가독성을 위해 사용하지 않은 것들을 제거 했으므로 여기에 전체 코드가 있습니다. http://codepad.org/K4Bocuht

+1

복사 생성자에 대해 읽고'char * '대신'std :: string'을 사용하십시오. 여기에 객체가 복사되고 * 할당 된 문자열이 손실됩니다. –

+0

당신이 그것을 잡지 못했다면,'Time' 클래스는'add()'에 여분의'Time ::'을 가지고 있습니다. – chris

답변

4

우선 캐릭터를 뒤섞기 위해 char*을 사용하고 있다는 사실을 극복 할 수 없습니다. 큰 소리로 외치기 위해 std::string. 이것은 일련의 문자를 다루는 표준화 된 방법입니다. 그것은 당신이 그것에 버리는 무엇이든간에 효율적으로 움직일 수 있습니다. 그것을 문자열 리터럴, char 배열, char * 또는 또 다른 문자열로 사용하십시오.

두 번째로, 교수님께서는 전문적인 도움이 필요합니다. "저급 직관력"을 좀 더 원시적 인 것으로 가르치는 것이 한 가지이지만, C++을 공부해야하는 학생들에게 이것을 강요하는 것은 나쁜 습관입니다.

지금 당장 문제가됩니다. 난 그냥 예를 들어, 귀하의 식기 세척기 생성자에 누워 원시, "알몸"포인터, char* str 최고 걸릴거야. 알다시피, 그것은 포인터, 즉 char 유형에 대한 포인터입니다. 포인터는 변수의 메모리 주소 (문제의 변수 유형의 첫 번째 바이트로 메모리에서 가장 주소 지정이 가능한 단위 임)를 저장합니다.

뚜렷한 차이가 매우 중요합니다. 왜? 왜냐하면 당신이 다른 것에 포인터를 할당 할 때, 당신은 실제 객체를 복사하지 않고 첫번째 바이트의 주소만을 복사하기 때문입니다. 결과적으로 동일한 객체를 가리키는 포인터 두 개를 얻게됩니다. ,

washer_id = new char [(strlen(str)+1)]; 

당신은 기본적으로 나 strlen (STR) 힙에 1 바이트를 할당하고 있습니다 : 당신이 따라

은 의심의 여지가, 좋은 기억 시민은, 당신은 아마이 돌봐 소멸자를 정의 시스템에 의해 관리되지 않고 유능한 손에 남아있는 것. 그러므로 이름, 힙. 한 무리의 물건, 그것에 대한 참조를 잃어 버리면, 다시는 찾을 수 없을 것입니다. 모든 것을 버릴 수 있습니다. (프로그램이 메인에서 돌아 오면 실제로 모든 누출이 발생합니다.) 따라서 시스템 사용을 마쳤 으면 시스템에 알리는 것이 사용자의 의무입니다. 그리고 당신은 소멸자를 정의했습니다.

... 불쾌한 큰하지만

하지만 ...이 계획에 문제가 있습니다. 당신은 생성자를 가지고 있습니다. 소멸자. 자원 할당 및 할당 해제를 관리합니다. 그러나 복사는 어떨까요?

Dishwasher siemens( 
     Pump(160, "011219991143"), 
     Motor(1300, "081220031201"), 
     "010720081032", 
     17.5); 

당신은 컴파일러는 암시 적으로 기본 복사 생성자, (이미 구축 된 개체에 대한) 복사 할당 연산자 및 소멸자를 만들려고 할 것을 아마 알고 있습니다. 암시 적으로 생성 된 소멸자는 수동으로 해제 할 필요가 없으므로 (동적 메모리와 책임을 논의했습니다) 비어 있습니다.

동적 메모리로 작업하고 텍스트를 저장하기 위해 다양한 크기의 바이트 블록을 할당하므로 더 긴 코드 쇼처럼 소멸자가 제자리에 있습니다. 그게 전부입니다 만, 우리는 여전히 암시 적으로 생성 된 복사 생성자와 변수의 직접 값을 복사하는 복사 할당 연산자를 처리합니다. 포인터가 값이 메모리 주소 인 변수이므로 암시 적으로 생성 된 복사 생성자 또는 복사 할당 연산자가 수행하는 작업은 해당 메모리 주소 (얕은 복사)를 복사하는 것입니다. 메모리의 단일 바이트 (및 연속 블록의 나머지 부분).

는 우리가 원하는 것은 입력 포인터의 메모리에 저장된 상기 대향하는 전체 복사본 새로운 메모리를 할당하고 실물 위에 복사 (또는 화합물 멀티 바이트 타입 등 배열) 인 주소. 이 방법을 사용하면 피사체의 수명이 둘러싸는 피사체의 수명과 관련되어있는 의 피사체를 가리키고에서 복사되는 피사체의 수명이 지켜지지 않습니다.

위의 예에서 스택에 임시로 생성되는 개체가 생성자가 실행 중일 때 생성 된 후 해당 개체가 해제되고 소멸자가 호출된다는 것을 알 수 있습니다.

Dishwasher::Dishwasher(Pump p, Motor m, char *str, float f) 
    : pump(p), motor(m), max_load(f) 
{ 
    washer_id = new char [(strlen(str)+1)]; 
    strcpy (washer_id,str); 
    time_built.id2time(washer_id); 
    date_built.id2date(washer_id); 
} 

초기화 목록, 복사를 수행 한 후 기본 값으로 객체를 초기화하지의 추가 혜택을 가지고 직접이 경우 암시 적으로 컴파일러에 의해 생성 된 복사 생성자를 (호출의 영광이 있기 때문에) :

pump(p)은 기본적으로 Pump :: Pump (const Pump &)를 호출하고 임시 객체가 초기화 된 값을 전달합니다. Pump 클래스에는 임시 객체에 넣은 문자열 리터럴의 첫 번째 바이트 주소 인 Pump(160, "011219991143")을 저장하는 char *가 포함되어 있습니다.

복사 생성자는 임시 개체를 가져 와서 명시 적으로 사용할 수있는 모든 데이터를 복사합니다. 즉, 전체 문자열이 아닌 char * 포인터에 포함 된 주소를 사용합니다. 따라서 두 장소를 같은 대상으로 향하게됩니다.

임시 객체가 스택에 있기 때문에 생성자가 처리를 마친 후에는 해제 된 릴리즈가되고 소멸자가 호출됩니다. 이 소멸자는 식기 세척기 객체를 생성하는 동안 배치 한 문자열을 실제로 파괴합니다. 이제 Dishwasher 객체 내의 Pump 객체에는 끝이없는 메모리 심음에서 손실 된 객체의 메모리 주소에 대한 포인터 인 매달린 포인터가 있습니다.

해결책?

생성자를 작성하고 할당 연산자 오버로드를 복사하십시오.펌프의 예에서 :

Pump(const Pump &pumpSrc) // copy constructor 

Pump& operator=(const Pump &pumpSrc) // copy assignment operator overload 

foreach 클래스를 수행합니다.

물론 소멸자 외에 이미 가지고있는 소멸자가 있습니다. 이 세 사람은 "규칙 3 :"이라는 엄지 법칙의 주인공입니다. 명시 적으로 선언해야하는 경우 은 나머지 부분도 명시 적으로 선언해야합니다.

아마도 부분은 기본적으로 책임지지 않습니다. 명백한 정의에 대한 필요성은 C++로 더 나아갈 때 실제로 아주 분명합니다. 예를 들어, 클래스가하는 일에 대해 생각하는 것은 명시 적으로 정의 된 모든 것이 필요한지 여부를 결정하는 좋은 방법입니다.

예 : 클래스는 메모리 조각을 가리키는 알몸 포인터에 의존하며 메모리 주소는 모두 가져옵니다. 문제의 객체의 첫 번째 바이트에 대한 메모리 주소 만 보유하는 단순 유형 지정 변수입니다.

개체를 파괴 할 때 누출을 방지하기 위해 할당 된 메모리를 해제하는 소멸자를 정의했습니다. 그러나 객체간에 데이터를 복사합니까? 당신이 본 것처럼 매우 가능성이 높습니다. 때로는 데이터를 할당하고 값을 복사 할 포인터 데이터 멤버에 메모리 주소를 저장하는 임시 개체를 만들 것입니다. 잠시 후, 그 임시는 언젠가 파괴 될 것이고, 당신은 그것으로 데이터를 잃어 버릴 것입니다. 당신이 남길 수있는 유일한 것은 쓸모없고 위험한 메모리 주소가있는 매달려있는 포인터입니다.

공식 면책 조항 : 일부 단순화로 인해 해당 주제에 대한 책을 쓸 수 없습니다. 또한 나는 항상 OP의 코드가 아닌 직접적으로 문제에 집중하려고 노력한다. 즉, 나는 사용 된 관행에 대해 언급하지 않는다. 코드는 끔찍하거나, 아름답거나 그 중간에있을 수 있습니다. 하지만 코드 나 OP를 돌아 보려고하지는 않습니다. 질문에 답하려고 할 때가 있습니다. 장기적으로 OP에 도움이 될 수 있다고 생각하는 것을 제안하기도합니다. 예, 우리는 정확하고 모든 것을 검증 할 수 있습니다 ... 또한 Peano 공리를 사용하여 숫자 세트와 기본 산술 연산을 정의 할 수 있습니다. 대담하게도 2 + 3 = 3 + 2 = 5라고 말할 수 있습니다. 즐거움의 양.

+0

Domagoj는 설명적인 긴 대답을 쓸 시간을 내 주셔서 대단히 감사합니다. 내 이해가 정말 잘되고이 줄이 커졌습니다. _pump (p)는 기본적으로 펌프를 호출합니다. : 펌프 (const 펌프 &) _ 모든 것을 명확하게 만들었습니다. 이제 답변을 드릴 때 새로운 구현 작업을하고 있습니다. 다시 한번 감사드립니다! PS : "=" 연산자를 복사하는 경우이 예제에서는 필요 없다고 생각합니까? – Anarkie

+1

@Anarkie 여기에서는 중요하지 않습니다 (내가 볼 수있는 한 멀리 사용하지 않으므로). 그러나 당신이 그것을 사용하는 것을 발견한다면, 당신이 그것을 정의했는지 확인하십시오.그렇지 않으면 다시 얕은 사본으로 다시 복사되며 같은 개체에 대한 두 개의 포인터로 다시 끝납니다. 나는 어쨌든, 그냥 재미와 학습을 위해 그것을 권하고 싶습니다. 연습은 완벽합니다. 세 가지 규칙을 준수하는 것이 일반적으로 좋은 아이디어입니다. 특히 확실하지 않은 경우 특히 그렇습니다. 행복한 코딩. =) –

3

귀하는 Rule of Three을 위반했습니다. 다음 두 가지 방법으로 문제를 해결할 수 있습니다.

  • 누드 포인터를 사용하지 마십시오. 귀하의 경우, 모든 인스턴스에서 char*std::string으로 바꾸면됩니다.
  • 각각 복사 생성자와 복사 할당 연산자를 구현하고 각각은 완전 복사를 수행해야하며 소멸자입니다.
+0

CCTOR 덕분에 고맙겠습니다.하지만 교수님이 std :: string을 사용하길 원하기 때문에 char *를 사용해야합니다 : ( – Anarkie

+1

왜 그 바보 같은 교수님들이 모든 것을 잘못 가르쳐 줍니까? – Griwes

+0

@Griwes - They 운전자의 면허를 얻기 전에 10 대들에게 가솔린을 어떻게 원유에서 증류 할 수 있는지, 또는 전기 기술자에게 구리 광석을 채취하는 법을 가르치는 것과 마찬가지로 –