레거시 코드에 래퍼가 있습니다.
class A{
L* impl_; // the legacy object has to be in the heap, could be also unique_ptr
A(A const&) = delete;
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
... // proper resource management here
};
이 레거시 코드에서 객체를 "중복"하는 함수는 스레드로부터 안전하지 않으므로 (같은 첫 번째 인수를 호출 할 때) const
래퍼에 표시되지 않습니다 . 현대 규칙을 따르는 것 같습니다 : https://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
이것은 duplicate
그렇지 않은 세부 사항을 제외하고 복사 생성자를 구현하는 좋은 방법처럼 보입니다 const
. 따라서 나는 이것을 직접 할 수 없다 :
class A{
L* impl_; // the legacy object has to be in the heap
A(A const& other) : L{other.duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
이 역설적 인 상황에서 벗어나는 방법은 무엇입니까?
( legacy_duplicate
쓰레드 안전하지는 않지만 객체가 종료되면 객체를 원래 상태로 유지한다는 것을 알고 있습니다. C 함수이므로 동작은 문서화되어 있지만 constness의 개념은 없습니다.)
가능한 많은 시나리오를 생각할 수 있습니다.
(1) 하나의 가능성은 일반적인 의미론을 가진 복사 생성자를 전혀 구현할 방법이 없다는 것입니다. (예, 객체를 움직일 수 있으며 그것이 필요한 것은 아닙니다.)
(2) 반면에 객체를 복사하는 것은 기본적으로 단순 유형을 복사하면 반 수정 된 상태에서 소스를 찾을 수 있다는 의미에서 스레드로부터 안전하지 않습니다.
class A{
L* impl_;
A(A const& other) : L{const_cast<A&>(other).duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(3) 또는 duplicate
const를 선언 하고 모든 상황에서 스레드 안전성에 대해 거짓말합니다. (모든 레거시 함수는 신경 쓰지 const
않으므로 컴파일러는 불평하지 않습니다.)
class A{
L* impl_;
A(A const& other) : L{other.duplicate()}{}
L* duplicate() const{L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(4) 마지막으로, 나는 논리를 따르고 논-스트레스 논증 을 취하는 복사 생성자를 만들 수 있습니다 .
class A{
L* impl_;
A(A const&) = delete;
A(A& other) : L{other.duplicate()}{}
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
이러한 객체는 일반적으로 그렇지 않기 때문에 많은 상황에서 작동한다는 것이 밝혀졌습니다 const
.
문제는 이것이 유효하거나 일반적인 경로입니까?
나는 그것들의 이름을 말할 수는 없지만, 비 const 사본 생성자를 갖는 길에서 직관적으로 많은 문제를 예상합니다. 아마도이 미묘함으로 인해 가치 유형으로 적합하지 않을 것입니다.
(5) 마지막으로, 이것은 과도한 것으로 보이며 런타임 비용이 가파르지만 뮤텍스를 추가 할 수 있습니다.
class A{
L* impl_;
A(A const& other) : L{other.duplicate_locked()}{}
L* duplicate(){
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
L* duplicate_locked() const{
std::lock_guard<std::mutex> lk(mut);
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
mutable std::mutex mut;
};
그러나 이것을 강요하면 비관 화처럼 보이고 수업이 더 커집니다. 확실하지 않습니다. 나는 현재 (4) 또는 (5) 또는 둘의 조합에 기대어 있습니다.
—— 편집
다른 옵션 :
(6) 중복 멤버 함수의 모든 비센스를 잊어 버리고 legacy_duplicate
생성자 를 호출 하고 복사 생성자가 스레드로부터 안전하지 않다고 선언하십시오. (필요한 경우 다른 유형의 스레드 안전 버전을 만듭니다. A_mt
)
class A{
L* impl_;
A(A const& other){legacy_duplicate(other.impl_, &impl_);}
};
편집 2
레거시 기능이 수행하는 작업에 적합한 모델 일 수 있습니다. 입력을 터치하면 첫 번째 인수가 나타내는 값과 관련하여 호출이 스레드로부터 안전하지 않습니다.
void legacy_duplicate(L* in, L** out){
*out = new L{};
char tmp = in[0];
in[0] = tmp;
std::memcpy(*out, in, sizeof *in); return;
}
legacy_duplicate
두 개의 다른 스레드에서 동일한 첫 번째 인수로 함수 를 호출 할 수 없습니다.
const
정말로 무엇을 의미 하는지 모르는 사람들 중 하나 일 것입니다 . :-) 나는 const&
수정하지 않는 한 내 사본 ctor에 대해 두 번 생각하지 않을 것 other
입니다. 나는 항상 스레드 안전성을 캡슐화를 통해 여러 스레드에서 액세스 해야하는 항목 위에 추가하는 것으로 생각하며 실제로 답변을 기대하고 있습니다.
L
새L
인스턴스 를 작성하여 수정 한 상태가 포함되어 있지 않습니까? 그렇지 않은 경우이 작업이 스레드로부터 안전하지 않다고 생각하는 이유는 무엇입니까?