2012-06-18 4 views
10

벡터 요소의 파기 순서가 C++ 표준 (Order of destruction of elements of an std::vector 참조)에 의해 정의되어 있지 않다는 것을 알고 있고 내가 검사 한 모든 컴파일러가 처음부터 끝까지이 파괴를 수행한다는 것을 알았습니다 - 역동적이고 정적 인 배열은 역으로 수행하기 때문에 나에게는 놀라운 일입니다 이 역순은 C++ 세계에서 꽤 자주 발생합니다.벡터 요소의 파괴 순서를 정의하는 것이 합리적일까요?

엄격히 말하면 : "컨테이너 구성원은 ... 예를 들어 멤버 함수 삽입 및 지우기 기능을 사용하여 임의의 순서로 생성 및 삭제할 수 있습니다."그리고 "컨테이너에 대해 일종의 로그를 유지하도록 투표하지 않습니다. 변화 ". 난 현재 벡터 소멸자 구현을 앞으로의 파괴에서부터 요소의 후방 파괴로 바꾸는 것에 찬성표를 던진다. 그리고이 규칙을 C++ 표준에 추가하십시오.

그 이유는 무엇입니까? 배열에서 벡터로 변경하면 이런 식으로 안전합니다.

실제 세계 예 : 뮤텍스 잠금 및 잠금 해제 순서는 매우 중요합니다. 잠금 해제가 발생하는지 확인하려면 ScopeGuard 패턴이 사용됩니다. 그러면 파괴 질서가 중요합니다. 이 예제를 고려하십시오. 이 - 벡터 원인 교착 상태에 배열로 전환 - 자신의 파괴 순서는 다릅니다해서 :

class mutex { 
public: 
    void lock() { cout << (void*)this << "->lock()\n"; } 
    void unlock() { cout << (void*)this << "->unlock()\n"; } 
}; 

class lock { 
    lock(const mutex&); 
public: 
    lock(mutex& m) : m_(&m) { m_->lock(); } 
    lock(lock&& o) { m_ = o.m_; o.m_ = 0; } 
    lock& operator = (lock&& o) { 
     if (&o != this) { 
      m_ = o.m_; o.m_ = 0; 
     } 
     return *this; 
    } 
    ~lock() { if (m_) m_->unlock(); } 
private: 
    mutex* m_; 
}; 

mutex m1, m2, m3, m4, m5, m6; 

void f1() { 
    cout << "f1() begin!\n"; 
    lock ll[] = { m1, m2, m3, m4, m5 }; 
    cout <<; "f1() end!\n"; 
} 

void f2() { 
    cout << "f2() begin!\n"; 
    vector<lock> ll; 
    ll.reserve(6); // note memory is reserved - no re-assigned expected!! 
    ll.push_back(m1); 
    ll.push_back(m2); 
    ll.push_back(m3); 
    ll.push_back(m4); 
    ll.push_back(m5); 
    cout << "f2() end!\n"; 
} 

int main() { 
    f1(); 
    f2(); 
} 

OUTPUT - F2로) (F1()

f1() begin! 
0x804a854->lock() 
0x804a855->lock() 
0x804a856->lock() 
0x804a857->lock() 
0x804a858->lock() 
f1() end! 
0x804a858->unlock() 
0x804a857->unlock() 
0x804a856->unlock() 
0x804a855->unlock() 
0x804a854->unlock() 
f2() begin! 
0x804a854->lock() 
0x804a855->lock() 
0x804a856->lock() 
0x804a857->lock() 
0x804a858->lock() 
f2() end! 
0x804a854->unlock() 
0x804a855->unlock() 
0x804a856->unlock() 
0x804a857->unlock() 
0x804a858->unlock() 
+1

IMHO 소프트웨어가 잘 설계된 경우 파괴 순서가 중요하지 않아야합니다. 소멸자가 호출되면 객체가 더 이상 사용되지 않거나 필요하지 않음을 의미합니다. 개체를 파괴하기 전에 개체가 일관성있는 상태 (이 경우 더 이상 사용되지 않음)에 있는지 확인해야합니다. – m0skit0

+0

우리는 또한 실행 순서가 중요 할 때 모든 컨테이너에 넣고 생성 된 코드를 삭제하는 것은 좋지 않다는 것을 알고 있습니다. // 풍자 고시 : 나는 우리 모두가 알고있는 진술로 조금 의심 스럽다. – stefaanv

+0

아마도 당신은 독서없이 대답했다. n ScopeGuard (http://stackoverflow.com/questions/48647/does-scopeguard-use-really-lead-to-better-code) 여기에서 사용한 파괴 명령이 중요합니다. 이것이 내가이 예를 사용한 이유입니다. – PiotrNycz

답변

4

난이 생각에서 파괴 순서 변경을 참조 C++의 또 다른 사례는 컴파일러 작성자에게 아키텍처에 가장 효율적인 컨테이너를 작성할 수있는 유연성을 제공합니다. 특정 순서로 파기를 요구하면 케이스의 0.001 %와 같은 편의성을 위해 성능이 저하 될 수 있습니다 (기본 주문이 적합하지 않은 다른 예제는 본적이 없습니다). 이 경우 vector은 인접한 데이터이므로 필자는 역순으로 반복하고 캐시를 반복적으로 누락하는 대신 지능적으로 미리보기 캐싱을 사용하는 하드웨어의 기능을 언급합니다.

컨테이너 인스턴스에 특정 파손 순서가 필요한 경우 다른 클라이언트에게 표준 기능에 잠재적으로 불이익을주지 않도록 직접 구현해야합니다.

+0

답장을 보내 주셔서 감사합니다. 전방과 후방의 파괴에는 성능상의 차이가 없다고 생각했습니다. – PiotrNycz

+0

Mark - 답에 따르면 - 배열과 멤버 변수에서 역순으로 소멸자를 호출하는 C++ 규칙은이 파괴가 순방향으로 그렇게 효율적이지 않은가? – PiotrNycz

+0

@ user1463922 성능에 미치는 영향이있을 수 있습니다. 확실히 멤버 변수의 경우 고정 된 순서의 건설/파괴가 바람직합니다. 컨테이너의 경우 다른 케이스와 선택 사항입니다. –

4

FWIW, libc++ 출력 :

f1() begin! 
0x1063e1168->lock() 
0x1063e1169->lock() 
0x1063e116a->lock() 
0x1063e116b->lock() 
0x1063e116c->lock() 
f1() end! 
0x1063e116c->unlock() 
0x1063e116b->unlock() 
0x1063e116a->unlock() 
0x1063e1169->unlock() 
0x1063e1168->unlock() 
f2() begin! 
0x1063e1168->lock() 
0x1063e1169->lock() 
0x1063e116a->lock() 
0x1063e116b->lock() 
0x1063e116c->lock() 
f2() end! 
0x1063e116c->unlock() 
0x1063e116b->unlock() 
0x1063e116a->unlock() 
0x1063e1169->unlock() 
0x1063e1168->unlock() 

이것은 의도적으로 이러한 방법을 구현 하였다. here을 정의 된 주요 기능은 다음과 같습니다 size() 축소해야 할 때마다

template <class _Tp, class _Allocator> 
_LIBCPP_INLINE_VISIBILITY inline 
void 
__vector_base<_Tp, _Allocator>::__destruct_at_end(const_pointer __new_last, false_type) _NOEXCEPT 
{ 
    while (__new_last != __end_) 
     __alloc_traits::destroy(__alloc(), const_cast<pointer>(--__end_)); 
} 

이 개인 구현 세부가 호출됩니다.

나는이 구현 세부 사항에 대해 긍정적이거나 부정적인 피드백을 아직받지 못했습니다.

+1

그런 구현이 있다는 것을 알면 좋습니다. – PiotrNycz

+0

Mark for forward destruction에 대한 이론적 근거에 대해 "하드웨어를 역순으로 반복하지 않고 캐시를 반복적으로 누락하는 대신 지능적으로 미리보기 캐싱을 활용하는 하드웨어 기능을 사용하는 방법에 대해 어떻게 생각하십니까?" libC++는 성능면에서 우수하다고 주장합니다 ... – PiotrNycz

+0

이것은 가능성이 있습니다. 그러나 나는 그런 차이를 느끼지 못했다. 나는 그것을 찾지도 않았다. 어떤 고객도이 분야의 성과 문제에 대해 불만을 토로하지 못했습니다 (다른 분야에서 성과 불만을 제기했습니다 - 일부는 이미 해결했으며, 일부는 여전히해야 할 일 목록에 있습니다). –