2014-12-18 6 views
6

나를 놀라게 한 코드를 발견했습니다. 는 본질적으로는이 패턴 다음 :이 코드는 년 동안 코드베이스되었습니다C++ 배치를 사용하여 객체를 두 번 생성 중입니다. 새 정의되지 않은 동작입니까?

class Foo 
{ 
    public: 
    //default constructor 
    Foo(): x(0), ptr(nullptr) 
    { 
     //do nothing 
    } 

    //more interesting constructor 
    Foo(FooInitialiser& init): x(0), ptr(nullptr) 
    { 
     x = init.getX(); 
     ptr = new int; 
    } 
    ~Foo() 
    { 
     delete ptr; 
    } 

    private: 
    int x; 
    int* ptr; 
}; 


void someFunction(FooInitialiser initialiser) 
{ 
    int numFoos = MAGIC_NUMBER; 
    Foo* fooArray = new Foo[numFoos]; //allocate an array of default constructed Foo's 

    for(int i = 0; i < numFoos; ++i) 
    { 
     new(fooArray+ i) Foo(initialiser); //use placement new to initialise 
    } 

    //... do stuff 

    delete[] fooArray; 
} 

를하고 문제가 발생하지 않았다 보인다. 누군가가 기본 생성자를 변경하여 두 번째 구성을 기대하지 않도록 할당 할 수 있으므로 분명히 나쁜 생각입니다. 두 번째 생성자를 동등한 초기화 메소드로 바꾸는 것만으로도 합리적인 것처럼 보일 것입니다. 예.

void Foo::initialise(FooInitialiser& init) 
{ 
    x = init.getX(); 
    ptr = new int; 
} 

여전히 리소스 유출 가능성이 있지만 적어도 방어적인 프로그래머는 정상적인 방법으로 사전 할당을 확인한다고 생각할 수 있습니다.

내 질문은 :

는 표준 또는 단순히 나쁜 생각에 의해 금지 /이 실제로 정의되지 않은 동작처럼 두 번 구성되어 있습니까? 정의되지 않은 동작을 사용하면 견적을 작성하거나 표준을 볼 수있는 적절한 곳으로 안내 할 수 있습니까?

+0

이 코드에서 valgrind를 사용해 보셨습니까? – zoska

+0

내가 보는 주된 문제점은'Foo'가 3 가지 규칙을 따르지 않는다는 것입니다. 기본 복사와 복사 할당 연산자는'Foo :: ptr'로 올바른 일을하지 않을 것입니다. – cdhowie

+0

@cdhowie 어쩌면 우리는 다른 사람들의 코드에 대해 최악의 상황을 가정해서는 안됩니다. OP는 단순히 질문을 할 필요가없는 코드를 잘라낸 것 같습니다. – anatolyg

답변

10

일반적으로 이러한 방식으로 새로운 배치를 사용하는 것은 좋은 생각이 아닙니다. 첫 번째 새 코드에서 이니셜 라이저를 호출하거나 새 코드 대신 이니셜 라이저를 호출하면 둘 다 제공 한 코드보다 더 좋은 형식으로 간주됩니다.

그러나이 경우 기존 개체 위에 새 게재 위치를 호출하는 동작이 잘 정의되어 있습니다.

프로그램은 객체가 점유 또는 명시 하는 비 단순 소멸자와 클래스 타입의 객체를 소멸자 호출하여 저장 를 재사용함으로써 개체의 수명을 끝낼 수있다. 평범하지 않은 소멸자가있는 클래스 유형의 객체 의 경우 객체가 차지하는 저장 공간이 재사용되거나 해제되는 저장 장치 전에 명시 적으로 소멸자를 호출해야하는 프로그램이 없습니다 (예 :). 그러나 소멸자에 대한 명시 적 호출이 이거나 삭제 표현식 (5.3.5)이 저장소를 해제하는 데 사용되지 않은 인 경우 소멸자는 암시 적으로 으로 호출되지 않고 부작용에 종속 된 프로그램이 이됩니다 소멸자가 생성 한 에는 정의되지 않은 동작이 있습니다.

그래서이 발생했을 때 :

Foo* fooArray = new Foo[numFoos]; //allocate an array of default constructed Foo's 

for(int i = 0; i < numFoos; ++i) 
{ 
    new(fooArray+ i) Foo(initialiser); //use placement new to initialise 
} 

배치 새로운 작업이 있었던 Foo의 수명을 종료하고,이 장소에 새로운 하나를 생성합니다. 많은 경우에 이것은 좋지 않을 수 있지만 소멸자가 작동하는 방식을 고려할 때 이것은 괜찮을 것입니다.

기존 개체에 새로운 호출 배치는 정의되지 않은 동작 일 수 있지만 특정 개체에 따라 다릅니다.

이것은 소멸자가 생성 한 "부작용"에 의존하지 않으므로 정의되지 않은 동작을 생성하지 않습니다.

개체의 소멸자의 유일한 "부작용"delete에 포함 된 int 포인터이지만, 배치 new가 호출 될 때 삭제 가능한 상태에 결코 오브젝트이 경우이다.

포함 된 int 포인터가 nullptr이 아닌 다른 값과 같을 수 있고 삭제가 필요할 수있는 경우 기존 개체 위에 배치 new을 호출하면 정의되지 않은 동작이 호출됩니다.

+3

정확하지만이 재사용을 끔찍한 생각이라고 생각합니다. –

+1

> 30 년이 넘는 코드베이스에는 많은 유사한 구조가 있으며, c에서 C++로 끝나지 않은 모든 결과입니다. –

+1

@MooingDuck 동의. 고마워, 나는 이것이 나쁜 생각 일 가능성이 있음을 분명히했다. 그러나이 경우에는 잘 정의되어있다. –