2009-03-13 2 views
2

저는 프로그래밍에 익숙하지 않지만 Java로 작업 한 후에 저는 C++로 돌아가고 포인터가 아닌 클래스 변수에 관해 혼란스러워합니다. 날이 실제로 야생에서 작동하는지 궁금해 경우, 또는 만들 C++의 참조와 포인터와 같은에 대한 이해의 나의 부족,C++에서 클래스 변수 설정에 대한 질문이 있습니다.

#include <iostream> 
#include <map> 

using namespace std; 

class Foo { 
    public: 
     Foo() { 
      bars[0]  = new Bar; 
      bars[0]->id = 5; 
     } 

     ~Foo() { } 

     struct Bar { 
      int id; 
     }; 

     void set_bars(map<int,Bar*>& b) { 
      bars = b; 
     } 

     void hello() { 
      cout << bars[0]->id << endl; 
     } 

    protected: 
     map<int,Bar*> bars; 
}; 

int main() { 
    Foo foo; 
    foo.hello(); 

    map<int,Foo::Bar*> testbars; 
    testbars[0]  = new Foo::Bar; 
    testbars[0]->id = 10; 

    foo.set_bars(testbars); 

    foo.hello(); 

    return(0); 
} 

내가 그러나 5 & (10)의 예상 출력을 얻을 : 다음 코드를 감안할 때 일단 testbars가 범위를 벗어나면 barf가됩니다. 물론, 여기에서는 프로그램이 끝나기 전에 테스트 바가 범위를 벗어나지 않을 것입니다.하지만 다른 클래스 함수에서 함수 변수로 만들어지면 어떻게 될까요? 어쨌든, 내 주요 질문은 내지도 클래스에 대한 포인터 변수로 bar 클래스를 만드는 것이 더 안전할까요?

답변

4

은 어쨌든, 난 내 주요 질문은 추측 것 더 잘/내가 포인터지도 맵에 로 바 클래스 변수를 생성하기에 안전?

아니요. C++은이 점에서 Java와 완전히 다르며 다른 점도 있습니다. 포인터를 사용하여 자신에게 새로운 객체를 많이 할당한다면, 당신은 아마 뭔가 잘못하고있는 것입니다. 일을 올바르게 수행하는 방법을 배우려면 Accelerated C++ 사본을 보유하는 것이 좋습니다. & Moo,

1

새로울 때마다 해당 삭제가 필요합니다. delete를 호출 한 후 메모리를 참조하려고한다면 - 프로그램의 위치는 실제로 "barf"가됩니다.

당신이 좋아하지 않는다면, 그렇게 간단 할 것입니다.

메모리 소유권을 명시 적으로 지정하고 모든 할당에 대해 동일한 할당 취소 작업을 수행하는 KNOW 클래스를 설계해야합니다. 할당 된 메모리를 다른 클래스/컨테이너가 삭제한다고 가정하지 마십시오.

희망이 도움이됩니다.

0

아래의 코드에서 바의지도를 전달할 수 있으며 클래스 외부의 바를 수정할 수 있습니다.

하지만. 하지만 set_bars를 다시 호출하지 않는 한.

하나의 개체가 Bars의 생성 및 삭제를 담당 할 때 더 좋습니다. 너의 경우에는 사실이 아니다.

원하는 경우 Bars * 대신 boost :: shared_ptr < 바를 사용할 수 있습니다. 그것은 Java와 비슷한 행동이 될 것입니다.

class Foo { 
public: 
    Foo() { 
     bars[0]  = new Bar; 
     bars[0]->id = 5; 
    } 

    ~Foo() { freeBarsMemory(); } 

    struct Bar { 
     int id; 
    }; 

    typedef std::map<int,Bar*> BarsList; 

    void set_bars(const BarsList& b) { 
     freeBarsMemory(); 
     bars = b; 
    } 

    void hello() { 
     std::cout << bars[0]->id << std::endl; 
    } 

protected: 
    BarsList bars; 

    void freeBarsMemory() 
    { 
     BarsList::const_iterator it = bars.begin(); 
     BarsList::const_iterator end = bars.end(); 

     for (; it != end; ++it) 
      delete it->second; 

     bars.clear(); 
    } 
}; 
+0

"bars.clear();"를 추가하면됩니다. freeBarsMemory()의 끝에서 사용하기에 조금 더 안전합니다. –

3

멤버 변수 bars은 "사전"과 같은/연관 배열 클래스의 별도 인스턴스입니다. 따라서 set_bars에 할당되면 b 매개 변수의 내용이 bars에 복사됩니다. 그러므로 상대 라이프 타임 인 footestbars에 대해 걱정할 필요가 없습니다. 이들은 독립적 인 "가치와 같은"인물입니다.

현재 삭제되지 않을 Bar 객체의 수명에 더 많은 문제가 있습니다. 어딘가에 코드를 추가하면 오브젝트 자체가 아니라 Bar 오브젝트의 주소가 복사되므로 다른 오브젝트가 두 개의 다른 맵에 의해 가리켜지기 때문에 다른 문제가 발생합니다.오브젝트가 삭제되면, 다른 맵은 오브젝트를 계속 참조합니다. 이것은 C++에서 전염병처럼 피해야 만하는 종류의 것입니다! new으로 할당 된 개체에 대한 알몸 포인터는 발생하기를 기다리는 재앙입니다.

참조 (&으로 선언 됨)는 개체 수명과 관련하여 포인터와 다르지 않습니다. 두 곳에서 동일한 객체를 참조 할 수있게하려면 포인터 또는 참조를 사용할 수 있지만 여전히 할당 해제 문제가 발생합니다.

최신 C++ 환경 (std::tr1)에 포함되어야하는 shared_ptr과 같은 클래스를 사용하면 할당 해제 문제를 해결할 수 있습니다. 그러나 순환 포인터 네트워크에서 문제가 발생할 수 있습니다 (예 : B와 B가 A를 가리킴). 자동으로 정리되지 않습니다.

+0

Bar 객체 내의 데이터를 조작하는 데 사용하는 다른 클래스가 있습니다. 해당 클래스가 Bar 변수의 값을 변경하면 Bar 객체를 다시 전달해야하는 변경 사항을 유지해야합니다. 지도에서 참조 (바 &)를 대신 사용해야합니까? –

+0

나는 내 대답에 조금 더 추가했습니다. NB. 저는 1994 년부터 C++을 상업적으로 사용 해왔고 Java/C# 프로그래머가 지금 그것을 배우기를 원한다는 것을 놀라게합니다! 오늘날 대부분의 일자리에 적합한 도구가 아닙니다. 간단한 일을 올바르게 수행하는 것은 불필요하게 고통 스럽습니다. –

0

저는 프로그래밍에 익숙하지 않지만 Java로 작업 한 후에 저는 C++로 돌아가고 포인터가 아닌 클래스 변수에 관해 혼란스러워합니다.

혼란은 힙에있는 데이터와 힙에있는 데이터의 조합에서 비롯된 것으로 보입니다. 이것은 혼란의 흔한 원인입니다.

게시 한 코드에서 bars은 (는) 포인터가 아닙니다. 그것이 클래스 스코프에 있기 때문에 그것을 포함하는 객체 (testbars)가 파괴 될 때까지 존재할 것입니다. 이 경우 testbars이 스택에 만들어 지므로 범위가 얼마나 깊게 중첩되었는지에 관계없이 범위를 벗어날 때 파괴됩니다. 그리고 testbars이 파괴되면 testbars의 하위 객체 (부모 클래스이거나 testbars 객체에 포함 된 객체)는 소멸자가 정확하게 정의 된 순서대로 정확하게 실행되도록합니다.

이것은 C++의 매우 강력한면입니다. 네트워크 연결을 열고, 힙에 메모리를 할당하고, 파일에 데이터를 쓰는 10 라인 생성자가있는 클래스를 상상해보십시오. 클래스의 소멸자가 그 모든 것을 취소했다면 (네트워크 연결을 닫고, 힙의 메모리 할당을 해제하고, 파일을 닫는 등) 상상해보십시오. 이제이 클래스의 객체를 만드는 것이 생성자의 중간에서 실패했다고 가정합니다 (예 : 네트워크 연결이 끊어짐). 소멸자의 어떤 줄이 성공한 생성자 부분을 실행 취소할지 프로그램에서 어떻게 알 수 있습니까? 이를 알 수있는 일반적인 방법이 없으므로 해당 객체의 소멸자가 실행되지 않습니다.

이제 10 개의 개체가 포함 된 클래스를 상상해보십시오. 각 개체의 생성자는 롤백해야하는 작업 (네트워크 연결 열기, 힙에 메모리 할당, 파일에 데이터 쓰기 등)을 수행합니다. 이러한 객체 각각에 대한 소멸자에는 작업을 롤백하는 데 필요한 코드가 포함됩니다 (네트워크 연결을 닫고, 객체를 할당 해제하고, 파일을 닫는 등). 5 개의 개체 만 성공적으로 생성되면 5 개의 개체 만 파괴해야하며 해당 소멸자 은 정확한 시점에으로 실행됩니다.

testbarsnew을 통해 힙에 작성된 경우 delete을 호출 할 때만 파괴됩니다. 일반적으로 객체가 생성 된 범위보다 오래 걸리지 않는 한 스택에 객체를 사용하는 것이 훨씬 쉽습니다.

어느 것이 나를 Foo::bar으로 가져옵니다. Foo::bars은 힙의 오브젝트를 나타내는 map입니다. 글쎄, 그것은이 코드 예제에서 힙에 할당 된 객체를 참조하는 포인터를 참조합니다 (포인터는 스택에 할당 된 객체를 참조 할 수도 있음).이 포인터가 참조하는 객체를 게시 한 예에서는 결코 delete d가 아니며 이러한 객체가 힙에 있기 때문에 작은 메모리 누수가 발생합니다 (운영 체제가 프로그램 종료시 정리하는). STL에 따르면, Foo::bar이 아니고,delete 포인터와 같다. 포인터는 파괴되었을 때를 가리킨다. Boost에는이 문제에 대한 몇 가지 해결책이 있습니다. 귀하의 경우에는 단순히 이러한 개체를 힙에 할당하지 않는 것이 가장 쉽습니다.

#include <iostream> 
#include <map> 

using std::map; 
using std::cout; 

class Foo { 
    public: 
     Foo() { 
      // normally you wouldn't use the parenthesis on the next line 
      // but we're creating an object without a name, so we need them 
      bars[0] = Bar(); 
      bars[0].id = 5; 
     } 

     ~Foo() { } 

     struct Bar { 
      int id; 
     }; 

     void set_bars(map<int,Bar>& b) { 
      bars = b; 
     } 

     void hello() { 
      cout << bars[0].id << endl; 
     } 

    protected: 
     map<int,Bar> bars; 
}; 

int main() { 
    Foo foo; 
    foo.hello(); 

    map<int,Foo::Bar> testbars; 
    // create another nameless object 
    testbars[0] = Foo::Bar(); 
    testbars[0].id = 10; 

    foo.set_bars(testbars); 
    foo.hello(); 
    return 0; 
} 
관련 문제