c++ - 프로그램 - 프로그래밍 언어 비유




왜 C++ 프로그래머는 'new'의 사용을 최소화해야합니까? (12)

Pre-C ++ 17 :

스마트 포인터로 결과를 래핑하는 경우에도 미묘한 누출이 발생하기 쉽습니다.

스마트 포인터로 객체를 래핑하는 것을 기억하는 "신중한"사용자를 생각해보십시오.

foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));

이 코드는 shared_ptrT1 또는 T2 이전 에 생성된다는 보장없기 때문에 위험합니다. 따라서 new T1() 또는 new T2() 중 하나가 성공한 후에 실패하면 첫 번째 객체는 공유 shared_ptr 이 없어서 할당을 해제하고 할당을 해제하기 때문에 유출됩니다.

해결책 : make_shared 사용 make_shared .

포스트 -C ++ 17 :

이것은 더 이상 문제가되지 않습니다. C ++ 17은 이러한 작업의 순서에 제약을가합니다.이 경우 new() 각 호출은 해당 작업을 수행하지 않고 해당 스마트 포인터를 즉시 생성해야합니다. 중에서. 즉, 두 번째 new() 가 호출 될 때까지는 첫 번째 객체가 이미 스마트 포인터에 래핑 된 것이 보장되므로 예외가 발생할 경우 누출을 방지 할 수 있습니다.

C ++에서 소개 한 새로운 평가 순서에 대한 자세한 설명은 Barry 가 다른 대답 으로 제공했습니다.

std :: list <std :: string> 사용할 때 스택 오버플로 질문 메모리 누수 std :: string 함께 발견 한 주석 중 하나를이 말한다 :

new 너무 많이 사용하지 마십시오. 나는 당신이 어디에서나 새로운 것을 사용한 이유를 볼 수 없습니다. C ++로 값으로 객체를 생성 할 수 있으며 언어 사용에 대한 큰 이점 중 하나입니다. 힙에 모든 것을 할당 할 필요는 없습니다. 자바 프로그래머처럼 생각하지 마라.

나는 그것이 그가 의미하는 바를 잘 모르겠습니다. 가능한 한 자주 C ++의 값으로 객체를 만들어야하는 이유와 내부적으로 어떤 차이가 있습니까? 대답을 잘못 해석 했습니까?


스택이 빠르고 간단하기 때문에

C ++에서 주어진 함수의 모든 로컬 스코프 객체에 대해 스택에 공간을 할당하는 단일 명령어가 필요하지만 그 메모리를 누설하는 것은 불가능합니다. 이 주석은 "스택을 사용하고 힙이 아닌" 과 같은 것을 말하고자했다 .


가능한 한 새로운 것을하지 않는 몇 가지 중요한 이유가 누락되었습니다.

연산자 new 는 비 결정적 실행 시간을 가짐

new 호출하면 OS가 새 물리적 페이지를 프로세스에 할당하게하지 않을 수 있습니다. 이렇게하면 자주 느려질 수 있습니다. 또는 이미 적절한 메모리 위치를 준비했을 수도 있습니다. 프로그램이 일관성 있고 예측 가능한 실행 시간을 필요로한다면 (예 : 실시간 시스템 또는 게임 / 물리 시뮬레이션에서) 시간이 중요한 루프에서 new 것을 피할 필요가 있습니다.

new 연산자는 암시적인 스레드 동기화입니다.

네가 나를 들었을 때, OS가 페이지 테이블이 일관성이 있는지 확인해야하며 new 호출로 스레드가 암시 적 뮤텍스 잠금을 획득하게됩니다. 당신이 계속해서 많은 쓰레드에서 new 것을 호출한다면 실제로 쓰레드를 직렬화하고있다. (32 개의 CPU를 가지고이 작업을 수행했다.

느린, 단편화, 오류가 발생하기 쉬운 것과 같은 나머지는 이미 다른 대답에 의해 언급되었습니다.


그것은 복잡합니다.

첫째, C ++은 가비지 수집되지 않습니다. 따라서 새로운 모든 경우 해당 삭제가 있어야합니다. 이 삭제를 넣지 않으면 메모리 누수가 발생합니다. 자, 다음과 같은 간단한 경우를 위해 :

std::string *someString = new std::string(...);
//Do stuff
delete someString;

이것은 간단합니다. 그러나 "Do stuff"가 예외를 throw하면 어떻게됩니까? 죄송합니다. 메모리 누출. '할 일'문제가 일찍 return 되면 어떻게됩니까? 죄송합니다. 메모리 누출.

그리고 이것은 가장 간단한 경우 입니다. 그 문자열을 누군가에게 돌려 주면, 이제 그 문자열을 삭제해야합니다. 그리고 그것을 인수로 전달한다면 그것을받는 사람이 그것을 삭제해야합니까? 언제 삭제해야합니까?

또는 다음 작업을 수행 할 수 있습니다.

std::string someString(...);
//Do stuff

delete 없습니다. 객체는 "스택"에서 만들어지며 일단 범위를 벗어나면 파괴됩니다. 객체를 반환하여 내용을 호출 함수로 전송할 수도 있습니다. 객체를 함수에 전달할 수 있습니다 (일반적으로 참조 또는 const-reference : void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis) .

모두 new 하지 않고 delete . 누가 메모리를 소유했는지 또는 누가 메모리를 삭제할 책임이 있는지에 대한 질문은 없습니다. 당신이하는 일 :

std::string someString(...);
std::string otherString;
otherString = someString;

otherString 에는 someString데이터 사본이 있음을 알 수 있습니다. 포인터가 아닙니다. 그것은 별도의 객체입니다. 그들은 같은 내용을 가지고 있을지도 모르지만 다른 것에 영향을 미치지 않으면 서 하나를 변경할 수 있습니다 :

someString += "More text.";
if(otherString == someString) { /*Will never get here */ }

아이디어 보시 겠어요?


대개의 경우, 그것은 자신의 약점을 일반적인 규칙으로 높이는 누군가입니다. new 연산자를 사용하여 객체를 만드는 것은 그다지 잘못되었습니다. 어떤 주장이있는 이유는 당신이 어떤 분야에서 그렇게해야한다는 것입니다 : 당신이 객체를 생성한다면, 그것이 파괴 될 것이라는 것을 확실히 할 필요가 있습니다.

그렇게하는 가장 쉬운 방법은 자동 스토리지에 객체를 생성하는 것입니다. 그래서 C ++은 범위를 벗어날 때 객체를 파괴하는 것을 알고 있습니다 :

 {
    File foo = File("foo.dat");

    // do things

 }

자, 끝 괄호 뒤에 해당 블록에서 떨어질 때 foo 는 범위를 벗어납니다. C ++에서 dtor을 자동으로 호출합니다. Java와 달리 GC가 찾길 기다릴 필요가 없습니다.

당신이 썼다면

 {
     File * foo = new File("foo.dat");

그것을 명시 적으로 일치 시키길 원할 것입니다.

     delete foo;
  }

File * 을 "스마트 포인터"로 할당하십시오. 그것에 대해 조심하지 않으면 누출로 이어질 수 있습니다.

응답 자체는 new 것을 사용하지 않으면 힙에 할당하지 않는다는 잘못된 가정을합니다. 사실, C ++에서는 그 사실을 모릅니다. 대부분의 경우, 포인터 하나가 스택에 할당되어 있음을 알 수 있습니다. 그러나 File의 구현이 다음과 비슷한 경우 고려하십시오.

  class File {
    private:
      FileImpl * fd;
    public:
      File(String fn){ fd = new FileImpl(fn);}

FileImpl여전히 스택에 할당됩니다.

그리고 네, 당신은

     ~File(){ delete fd ; }

수업에도. 그것 없이는 힙에 분명히 할당하지 않았더라도 힙에서 메모리가 누출됩니다.


두 가지 이유 :

  1. 이 경우에는 불필요합니다. 불필요하게 코드를 복잡하게 만듭니다.
  2. 힙에 공간을 할당하므로 나중에 메모리를 delete 해야한다는 것을 기억해야합니다. 그렇지 않으면 메모리 누수가 발생합니다.

포스터는 stack 아닌 heap You do not have to allocate everything on the 말했습니다.

기본적으로 오브젝트는 할당 자의 작업이 포함 된 힙 기반 할당보다는 싼 스택 할당 비용 때문에 스택에 할당됩니다 (물론 오브젝트 크기가 허용하는 경우). 자세한 표시가 필요합니다. 힙에 할당 된 데이터를 관리합니다.


핵심 힙은 힙의 객체가 단순한 값보다 항상 사용하고 관리하기가 어렵다는 것입니다. 읽기 쉽고 유지하기 쉬운 코드 작성은 항상 심각한 프로그래머의 최우선 과제입니다.

또 다른 시나리오는 우리가 사용하는 라이브러리가 가치 의미론을 제공하고 동적 할당을 필요로하지 않는다는 것입니다. Std::string 은 좋은 예입니다.

그러나 객체 지향 코드의 경우 포인터를 사용합니다. 즉 포인터를 사용하면 사전에 new 포인터를 만들어 포인터를 사용해야합니다. 리소스 관리의 복잡성을 단순화하기 위해 스마트 포인터와 같이 가능한 한 간단하게 만들 수있는 수십 가지 도구가 있습니다. 객체 기반의 패러다임 또는 일반적인 패러다임은 값 의미를 가정하고 포스터가 명시한 것처럼 new 것도 거의 필요하지 않습니다.

전통적인 디자인 패턴, 특히 GoF 책에 언급 된 패턴은 전형적인 OO 코드이므로 new 패턴을 많이 사용합니다.


new() 는 가능한 한 작은 것으로 사용하면 안됩니다. 가능한 한 신중하게 사용해야합니다. 그리고 실용주의에 의해 지시 된대로 필요한만큼 자주 사용되어야합니다.

암묵적인 파괴에 의존하는 스택상의 객체 할당은 간단한 모델입니다. 오브젝트의 필수 범위가 해당 모델에 적합한 경우 delete() 및 NULL 포인터 확인과 함께 new() 를 사용할 필요가 없습니다. 스택에 수명이 짧은 오브젝트 할당이 많은 경우에는 힙 분할의 문제점을 줄여야합니다.

그러나 개체의 수명이 현재 범위를 넘어서는 경우 new() 가 올바른 대답입니다. delete() 와 NULL 포인터의 가능성을 언제, 어떻게 호출하는지, 삭제 된 객체와 포인터를 사용하여 얻은 다른 모든 문제를주의 깊게 살펴보십시오.


new 의해 생성 된 객체는 결국 누출되지 않도록 delete 해야합니다. 소멸자는 호출되지 않으며 메모리는 해제되지 않습니다. C ++에는 가비지 수집이 없으므로 문제가됩니다.

값으로 생성 된 객체 (예 : 스택)는 범위를 벗어나면 자동으로 종료됩니다. 소멸자 호출은 컴파일러에 의해 삽입되고 메모리는 함수가 반환 될 때 자동으로 해제됩니다.

auto_ptr , shared_ptr 과 같은 스마트 포인터는 매달려있는 참조 문제를 해결하지만 규율을 코딩하고 다른 문제 (복사 가능성, 참조 루프 등)가 필요합니다.

또한, 다중 스레드가 심한 시나리오에서 new 것은 스레드 간의 경합 지점입니다. new 과용 한 경우 성능에 영향을 미칠 수 있습니다. 스택 개체 생성은 각 스레드가 자체 스택을 가지고 있으므로 정의에 따라 스레드 로컬입니다.

값 객체의 단점은 호스트 함수가 반환되면 사라진다는 것입니다. 값을 복사하거나 값을 반환하는 경우에만 호출자에게 참조를 전달할 수 없습니다.



  • C ++은 자체 메모리 관리자를 사용하지 않습니다. C #과 같은 다른 언어, Java는 가비지 컬렉터를 통해 메모리를 처리합니다.
  • 운영 체제 루틴을 사용하여 메모리를 할당하고 새로운 / 삭제를 너무 많이 사용하면 C ++은 사용 가능한 메모리를 조각 낼 수 있습니다.
  • 모든 응용 프로그램에서 메모리를 자주 사용하는 경우 미리 할당하고 필요하지 않을 때 해제하는 것이 좋습니다.
  • 부적절한 메모리 관리로 인해 메모리 누수가 발생할 수 있으며 추적하기가 정말 어렵습니다. 따라서 기능 범위 내에서 스택 객체를 사용하는 것이 입증 된 기술입니다
  • 스택 객체 사용의 단점은 함수를 반환 할 때 객체의 사본을 여러 개 생성한다는 것입니다. 그러나 스마트 컴파일러는 이러한 상황을 잘 알고 있으며 성능을 위해 잘 최적화되어 있습니다.
  • 메모리가 두 개의 다른 장소에 할당되고 해제되는 경우 C ++에서는 정말 지루합니다. 릴리스에 대한 책임은 항상 질문이며 대부분 우리는 자주 액세스 할 수있는 포인터, 스택 객체 (최대 가능) 및 auto_ptr (RAII 객체)와 같은 기술을 사용합니다.
  • 가장 좋은 점은 메모리를 제어 할 수 있다는 것입니다. 최악의 경우는 응용 프로그램에 대해 부적절한 메모리 관리를 사용하면 메모리를 제어 할 수 없다는 것입니다. 메모리 손상으로 인한 크래시는 가장 까다롭고 추적하기 어렵습니다.






c++-faq