C ++을 오래 전에 배웠을 때, C ++의 요점 중 하나는 루프에 "루프 불변"이있는 것처럼 클래스에도 객체의 수명과 관련된 불변이 있다는 것입니다. 개체가 살아있는 동안 생성자에 의해 확립되고 메소드에 의해 보존되어야하는 것들. 인 캡슐 레이션 / 액세스 제어는 변하지 않는 것을 적용하는 데 도움이됩니다. RAII는이 아이디어로 할 수있는 일입니다.
C ++ 11부터 이제 이동 의미론이 있습니다. 이동을 지원하는 클래스의 경우 객체에서 이동해도 수명이 공식적으로 종료되지 않습니다. 이동은 "유효한"상태로 유지됩니다.
클래스 를 디자인 할 때 클래스의 불변이 클래스가 이동하는 지점까지만 보존되도록 디자인하면 나쁜 습관 입니까? 또는 더 빨리 갈 수 있다면 괜찮 습니다.
구체적으로, 복사 할 수 없지만 이동 가능한 자원 유형이 있다고 가정하십시오.
class opaque {
opaque(const opaque &) = delete;
public:
opaque(opaque &&);
...
void mysterious();
void mysterious(int);
void mysterious(std::vector<std::string>);
};
그리고 어떤 이유로 든이 객체에 대해 복사 가능한 래퍼를 만들어야 기존의 일부 디스패치 시스템에서 사용할 수 있습니다.
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { o_->mysterious(); }
void operator()(int i) { o_->mysterious(i); }
void operator()(std::vector<std::string> v) { o_->mysterious(v); }
};
이 copyable_opaque
객체에서, 구축시에 설정된 클래스의 불변은 o_
기본 ctor가 없기 때문에 멤버가 항상 유효한 객체를 가리키고 복사 ctor가 아닌 유일한 ctor가이를 보장한다는 것입니다. 모든 operator()
방법은이 불변이 유지되는 것으로 가정하고 나중에 유지합니다.
그러나 객체가 이동 o_
하면 아무 것도 가리 키지 않습니다. 그리고 그 후에 메소드를 호출 operator()
하면 UB / 충돌이 발생합니다.
객체가 이동되지 않은 경우에는 고정자가 dtor 호출까지 그대로 유지됩니다.
가설 적으로, 나는이 수업을 썼고, 몇 달 후, 나의 가상의 동료가 UB를 경험했다고 가정 해 봅시다. 왜냐하면 이러한 많은 객체들이 어떤 이유로 셔플되는 복잡한 기능에서, 그는 이런 것들 중 하나에서 옮겨져 나중에 그 방법. 하루가 끝났을 때 분명히 그의 잘못 이었지만이 수업은 "나쁘게 설계 되었습니까?"
생각 :
좀비를 만지면 폭발하는 좀비 객체를 만드는 것은 일반적으로 C ++에서 좋지 않은 형태입니다.
어떤 객체를 만들 수 없다면 불변 값을 설정할 수 없으며 ctor에서 예외를 던집니다. 어떤 방법으로 불변 값을 보존 할 수 없으면 어떻게 든 오류를 표시하고 롤백합니다. 이동 한 객체와 다른 점이 있습니까?헤더에 "이 오브젝트가 이동 된 후 그것을 파괴하는 것 외에 다른 것을하는 것은 불법 (UB)"이라고 문서화하는 것으로 충분합니까?
각 메소드 호출에서 유효하다고 계속 주장하는 것이 더 낫습니까?
이렇게 :
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { assert(o_); o_->mysterious(); }
void operator()(int i) { assert(o_); o_->mysterious(i); }
void operator()(std::vector<std::string> v) { assert(o_); o_->mysterious(v); }
};
단언은 행동을 실질적으로 개선하지 않으며 속도 저하를 유발합니다. 프로젝트가 항상 어설 션으로 실행하는 대신 "릴리스 빌드 / 디버그 빌드"체계를 사용하는 경우 릴리스 빌드의 검사 비용을 지불하지 않기 때문에 이것이 더 매력적이라고 생각합니다. 실제로 디버그 빌드가 없다면, 이것은 꽤 매력적이지 않은 것 같습니다.
- 클래스를 복사 가능하지만 이동 가능하게 만드는 것이 더 낫습니까?
이 또한 나쁘게 보이고 성능 저하를 유발하지만 "불변"문제를 간단하게 해결합니다.
여기서 관련 "모범 사례"로 고려할 사항은 무엇입니까?