2010-02-09 2 views
2

저는 C++을 배우면서 실제 데이터 형식을 구현하기 시작했습니다. 첫 번째 스택 (이것은 마음에 봄 처음이었습니다). 몇 가지 프로그래밍을했는데 작동하지만 이제는 내가해야 할 일에 대한 의견이 필요합니다. 특정 물건이나 다른 프로 팁을 삭제하는 것과 같습니다. 나는 무엇을해야하고 왜 그렇게해야합니까?더 나은 프로그래밍 실무를 묻는 질문이 입력되었습니다

template <class T> 
class Stack 
{ 
private: 
    int* values; 
    int capacity; 
    int itemsOnStack; 

public: 

    /////////////////// 
    Stack() 
    { 
     Stack(32); 
    } 

    /////////////////// 
    Stack(const int sz) 
    { 
     values = new T[sz]; 
     capacity = sz; 
     itemsOnStack = 0; 
    } 

    ~Stack() 
    { 
     values = 0; 
       // delete? 
    } 

    //////////////////// 
    void Push(const T& item) 
    { 
     *(values + itemsOnStack) = item; 

     itemsOnStack++; 

     if(itemsOnStack > capacity) 
     { 
      capacity *= 2; 

      T* temp = new T[capacity]; 
      temp = values; 
      values = new T[capacity]; 
      values = temp;   
     } 
    } 

    /////////////////// 
    T Pop() 
    { 
     if(itemsOnStack > 0) 
     { 
      int current = --itemsOnStack; 
      return *(values + current); 
     } 
     return NULL; // ? good? 
    } 

    /////////////////// 
    T Peek() 
    { 
     if(itemsOnStack > 0) 
     { 
      int current = itemsOnStack - 1; 
      return *(values + current); 
     } 
     return NULL; // find something better here or shouldnt? 
    } 

    /////////////////// 
    int Count() 
    { 
     return itemsOnStack; 
    } 

    /////////////////// 
    int Capacity() 
    { 
     return capacity; 
    } 

    /////////////////// 
    bool IsEmpty() 
    { 
     return itemsOnStack == 0; 
    } 
}; 
+2

코드에 p 개의 LOTS가 있습니다. 흠집. 어떤 C++ 책을 배우고 있습니까? –

+1

단지 맛의 문제지만 나는 포인터 색인을위한'values ​​[current]'문법을 선호한다. – Manuel

+1

전체 답변을 게시하지는 않겠지 만 한 가지 힌트 : 귀하의 클래스는 현재'T = int' 일 때만 작동하는 것으로 보입니다. 'values'는'int *'대신'T *'형을 가져야합니다. 그렇지 않으면'Stack'을 템플릿 클래스로 쓰는 데 별다른 의미가 없습니다. – stakx

답변

7

이 코드에 대한 수정의 첫 번째 패스를 했습니까 그것을 쓰기 전에 배열의 크기를 확인해야합니다 :

template <class T> 
class Stack 
{ 
private: 
    int* values; 
    int capacity; 
    int itemsOnStack; 

public: 

    //Stack() : 
    //{ 
    // Stack(32); // doesn't do what you expect. This would create an unnamed temporary stack object 
    //} 

    Stack(const int sz = 32) // C++ doesn't yet have delegating constructors. You can't call one ctor from another. But in this case, a simple default parameter can be used instead 
    : values(new T[sz]), capacity(sz), itemsOnStack() {} // use the initializer list for initializing members 

    ~Stack() 
    { 
     delete[] values; // you allocated it, so you delete it as well 
    } 

    //////////////////// 
    void Push(const T& item) 
    { 
     values[itemsOnStack] = item; // cleaner syntactically than your version 
    // *(values + itemsOnStack) = item; 

     ++itemsOnStack; // prefer pre-increment by default. 

     if(itemsOnStack > capacity) // you need to check this before writing the element. Move this to the top of the function 
     { 
      int newCapacity = capacity * 2; 

      // what's this supposed to do? You're just copying pointers around, not the contents of the array 
      T* temp = new T[newCapacity ]; 
      std::copy(values, values+capacity, temp); // copy the contents from the old array to the new one 
      delete[] values; // delete the old array 
      values = temp; // store a pointer to the new array 
      capacity = newCapacity; 
     } 
    } 

    /////////////////// 
    T Pop() 
    { 
     T result = Peek(); // you've already got a peek function. Why not use that? 
     --itemsOnStack; 
     return result; 
    } 

    /////////////////// 
    T Peek() 
    { 
     if(itemsOnStack > 0) 
     { 
      int current = itemsOnStack - 1; 
      return values[current]; // again, array syntax is clearer than pointer arithmetics in this case. 
     } 
//  return NULL; // Only pointers can be null. There is no universal "nil" value in C++. Throw an exception would be my suggestion 
      throw StackEmptyException(); 
    } 

    /////////////////// 
    int Count() 
    { 
     return itemsOnStack; 
    } 

    /////////////////// 
    int Capacity() 
    { 
     return capacity; 
    } 

    /////////////////// 
    bool IsEmpty() 
    { 
     return itemsOnStack == 0; 
    } 
}; 

상황이 해결 왼쪽 :

생성자 및 대입 연산자를 복사하십시오. 나는이 작업을 수행하려고하면 지금이 순간, 그것은 끔찍하게 깰 수있을 :

Stack<int> s; 
Stack<int> t = s; // no copy constructor defined, so it'll just copy the pointer. Then both stacks will share the same internal array, and both will try to delete it when they're destroyed. 
Stack<int> u; 
u = s; // no assignment operator, so much like above, it'll blow up 

헌장의 정확성 :
가 나는 const Stack<T>Peek() 또는 Count()를 호출 할 수 있어야하지 않나요를?

개체 수명 :
스택에서 요소를 팝핑해도 요소의 소멸자가 호출되지 않습니다. 요소를 푸시해도 요소의 생성자는 호출되지 않습니다. 단순히 배열을 확장하면 모든 새 요소에 대해 즉시 기본 생성자가 호출됩니다. 생성자는 사용자가 befre가 아닌 요소를 삽입 할 때 호출되어야하고, 요소가 제거되면 즉시 소멸자가 호출되어야합니다.

적절한 테스트 :
어떤 방식 으로든 컴파일, 실행, 테스트 또는 디버깅하지 않았습니다. 그래서 나는 몇몇 버그를 놓쳤을 가능성이 매우 높았고 몇 가지 새로운 버그를 소개했습니다. ;)

+0

[] 값을 삭제 하시겠습니까? –

+0

doh yeah, fixed it. – jalf

+0

+1의 임무 이상! – stusmith

1

소멸자에서는 0으로 설정하는 대신 values을 삭제해야합니다. push 메서드에서

의 배열은 크기를 조정할 수 없습니다. 크기 조정 배열에

대한 추가 정보 : 요한이 언급 한 것을 http://www.daniweb.com/forums/thread13709.html#

4

이와 함께 몇 가지 이름을하려면 : 해당되는 경우

1.) 초기화 목록
2를 사용)는 const 멤버 함수를 사용합니다.
3.) 표준과보다 가깝게 정렬 된 함수 이름을 사용하십시오. (빈() IsEmpty() 대신)
4.) 당신은 거대한 메모리 누수가 있습니다.

+1

가상 소멸자를 제외하고 좋은 조언. 왜 그가 그걸 원했을까요? 맹목적으로 서브 클래 싱을 장려하는 것은 좋은 생각이 아닙니다. 클래스가 특별히 그것을 위해 설계되지 않았다면, 가상 소멸자를 가져서는 안됩니다. – jalf

+0

그래, 나는 거기에 조금 밖에 갔다. 댓글 주셔서 감사합니다. –

0

Push() 당신의 소멸자에서 메모리를 파괴 : 당신은

3

특히 스택 객체를 복사, 할당 또는 푸시 할 때 많은 메모리 누수가 발생합니다. 학습 연습이라 할지라도 자신의 메모리 관리 (C++로 배워야하지만 한 번에 한 단계 씩)가 필요하다는 점을 명확하게 제시 할 것을 제안합니다. 대신 원시 C 스타일의 값 배열보다는 std :: vector 또는 std :: list를 사용하십시오. 이것은 소멸자, 복사 생성자, 할당 연산자 및 Push() 메서드가 메모리를 누설하지 않는다는 것을 의미합니다. 기본적으로 올바른 작업을 수행합니다 (나는 최소 서프라이즈의 원칙). C++ 학습에서 가장 중요한 부분 중 하나는 STL을 사용하는 방법을 아는 것입니다. 처음에는 포인터와 새로운 []에 대해 걱정하고 나중에 []를 삭제하십시오.

복사 생성자, 할당 연산자 및 소멸자를 읽으면서 값이 실제로 복사되고 삭제되는 방법을 알 수 있습니다. 클래스 작성하십시오 :

class Noisy { 
    public: 
    Noisy() { std::cout << "Constructor" << std::endl; } 
    ~Noisy() { std::cout << "Destructor" << std::endl; } 
    Noisy& operator=(const Noisy& other) {std::cout << "Assign" << std::endl; } 
    Noisy(const Noisy& other) {std::cout << "Copy constructor" << std::endl; } 
}; 

을 다음 Stack<Noisy>에 몇 가지 작업을 수행합니다

Stack<Noisy> stack(10); 
stack.Push(Noisy()); 
Stack<Noisy> otherStack(stack); 
Stack<Noisy> thirdStack; 
thirdStack=stack; 

을하고 있는지 당신이 콘솔에 표시되는 내용과 일치를 수행해야한다고 생각 무엇을 기대 창문.

+0

감사합니다. STL로 넘어갑니다. – Oxymoron

0

멤버 변수 네 개의 명명 규칙을 사용해야합니다. 우리는 등

int *m_values; 
int m_capactity; 

사용 그리고 왜

*(values + itemsOnStack) = item; 

을 나는 그들이 정확히 같은 일을 할 당신이

values[itemsOnStack] = item; 

을 의미 가정하지만 첫 번째가 훨씬 더 않는다 자연어

+0

m_ 을 사용하는 것은 사용하지 않기로 결정한 것입니다. 나도 이걸 사용하거나 전혀 사용하지 않을 수도 있습니다. IMHO 코드에 많은 명확성을 추가하지 않습니다. – Oxymoron

+1

심각하게? 업계에서 수락 한 모범 사례 큰 시간을 놓치고 있습니다. this-> 사용에 대한 요점은 부적절합니다. 왜냐하면이 옵션은 선택 사항이기 때문에 여러분은 그 일을 그만 둘 것이고, 확실히 공동 개발자는 결코 그것을하지 않을 것입니다. 당신은 "나는 모든 줄에 'foo는 회원이고, bar는 로컬'이라고 말하면서 주석을 달 수 있습니다. 나를 믿어 라. 너는 네킹 컨벤션을 선택했다면 행복해질 것이다. – pm100

+0

아마도 .NET 배경 때문에, 나는 backing fields로 속성을 약식으로 사용했기 때문일 것이다. public string Name {get; 개인 집합; }. 물론, 간결한 코딩 표준의 필요성에 관해 논쟁하고 싶지는 않습니다. – Oxymoron