사소한 C ++ const 관련 중복을 피하기 위해 const_cast는 작동하지만 non-const를 반환하는 개인 const 함수는 그렇지 않은 경우가 있습니까?
Scott Meyers의 Effective C ++ 항목 3에서 정적 캐스트와 결합 된 const_cast는 중복 코드를 피하는 효과적이고 안전한 방법 일 수 있다고 제안합니다.
const void* Bar::bar(int i) const
{
...
return variableResultingFromNonTrivialDotDotDotCode;
}
void* Bar::bar(int i)
{
return const_cast<void*>(static_cast<const Bar*>(this)->bar(i));
}
Meyers는 const 함수를 non-const 함수로 호출하는 것이 위험하다고 설명합니다.
아래 코드는 다음과 같은 반례입니다.
- Meyers의 제안과 달리, 때로는 정적 캐스트와 결합 된 const_cast가 위험합니다
- 때로는 const 함수가 비 const를 호출하는 것이 덜 위험합니다.
- 때로는 const_cast를 사용하는 두 가지 방법으로 잠재적으로 유용한 컴파일러 오류 숨기기
- const_cast를 피하고 추가 const 개인 멤버가 const가 아닌 것을 반환하는 것은 또 다른 옵션입니다.
코드 복제를 피하는 const_cast 전략 중 하나가 모범 사례로 간주됩니까? 대신 개인 방법 전략을 원하십니까? const_cast는 작동하지만 개인 메소드는 작동하지 않는 경우가 있습니까? 다른 옵션 (복제 외에)이 있습니까?
const_cast 전략에 대한 나의 관심은 코드를 작성할 때 코드가 정확하더라도 나중에 유지 관리 중에 코드가 잘못 될 수 있고 const_cast가 유용한 컴파일러 오류를 숨길 수 있다는 것입니다. 일반적인 개인 기능이 일반적으로 더 안전한 것 같습니다.
class Foo
{
public:
Foo(const LongLived& constLongLived, LongLived& mutableLongLived)
: mConstLongLived(constLongLived), mMutableLongLived(mutableLongLived)
{}
// case A: we shouldn't ever be allowed to return a non-const reference to something we only have a const reference to
// const_cast prevents a useful compiler error
const LongLived& GetA1() const { return mConstLongLived; }
LongLived& GetA1()
{
return const_cast<LongLived&>( static_cast<const Foo*>(this)->GetA1() );
}
/* gives useful compiler error
LongLived& GetA2()
{
return mConstLongLived; // error: invalid initialization of reference of type 'LongLived&' from expression of type 'const LongLived'
}
const LongLived& GetA2() const { return const_cast<Foo*>(this)->GetA2(); }
*/
// case B: imagine we are using the convention that const means thread-safe, and we would prefer to re-calculate than lock the cache, then GetB0 might be correct:
int GetB0(int i) { return mCache.Nth(i); }
int GetB0(int i) const { return Fibonachi().Nth(i); }
/* gives useful compiler error
int GetB1(int i) const { return mCache.Nth(i); } // error: passing 'const Fibonachi' as 'this' argument of 'int Fibonachi::Nth(int)' discards qualifiers
int GetB1(int i)
{
return static_cast<const Foo*>(this)->GetB1(i);
}*/
// const_cast prevents a useful compiler error
int GetB2(int i) { return mCache.Nth(i); }
int GetB2(int i) const { return const_cast<Foo*>(this)->GetB2(i); }
// case C: calling a private const member that returns non-const seems like generally the way to go
LongLived& GetC1() { return GetC1Private(); }
const LongLived& GetC1() const { return GetC1Private(); }
private:
LongLived& GetC1Private() const { /* pretend a bunch of lines of code instead of just returning a single variable*/ return mMutableLongLived; }
const LongLived& mConstLongLived;
LongLived& mMutableLongLived;
Fibonachi mCache;
};
class Fibonachi
{
public:
Fibonachi()
{
mCache.push_back(0);
mCache.push_back(1);
}
int Nth(int n)
{
for (int i=mCache.size(); i <= n; ++i)
{
mCache.push_back(mCache[i-1] + mCache[i-2]);
}
return mCache[n];
}
int Nth(int n) const
{
return n < mCache.size() ? mCache[n] : -1;
}
private:
std::vector<int> mCache;
};
class LongLived {};
답변
반환 된 ptr / reference가 const인지에 따라 다른 const와 non-const 멤버 함수를 구현할 때 가장 좋은 DRY 전략은 다음과 같습니다.
- 접근 자를 작성하는 경우 접근자가 정말로 필요한지 고려하십시오. cmaster ‘s answer 및 http://c2.com/cgi/wiki?AccessorsAreEvil을 참조하십시오 .
- 코드가 사소한 경우 코드를 복제하십시오 (예 : 멤버를 반환)
- const 관련 중복을 피하기 위해 const_cast를 사용하지 마십시오
- 사소한 중복을 피하려면 const 및 non-const 공용 함수가 호출하는 non-const를 리턴하는 개인 const 함수를 사용하십시오.
예 :
public:
LongLived& GetC1() { return GetC1Private(); }
const LongLived& GetC1() const { return GetC1Private(); }
private:
LongLived& GetC1Private() const { /* non-trivial DRY logic here */ }
이것을 const가 아닌 const를 리턴 하는 private const 함수 라고하자 .
이것은 컴파일러가 잠재적으로 유용한 검사를 수행하고 const 관련 오류 메시지를보고 할 수 있도록하는 동시에 중복을 피하기위한 최선의 전략입니다.
답변
그렇습니다. 맞습니다. const-correctness를 시도하는 많은 C ++ 프로그램은 DRY 원칙을 심각하게 위반하고 있으며, non-const를 반환하는 개인 구성원조차도 편의를 위해 너무 복잡합니다.
그러나 한 가지 관찰은 그리워합니다. const-correctness로 인한 코드 복제는 데이터 멤버에 다른 코드 액세스를 제공하는 경우에만 문제가됩니다. 이것은 그 자체로 캡슐화를 위반하는 것입니다. 일반적으로 이러한 종류의 코드 복제는 대부분 간단한 접근 자에서 발생합니다 (결국 기존 멤버에 대한 액세스를 전달하는 경우 반환 값은 일반적으로 계산 결과가 아님).
내 경험은 좋은 추상화에는 접근자가 포함되지 않는 것입니다. 결과적으로 데이터 멤버에 대한 액세스를 제공하는 대신 실제로 무언가를 수행하는 멤버 함수를 정의하여이 문제를 피할 수 있습니다. 데이터 대신 행동을 모델링하려고합니다. 이것의 주요 목적은 객체를 데이터 컨테이너로 사용하는 대신 실제로 클래스와 개별 멤버 함수 모두에서 추상화를 얻는 것입니다. 그러나이 스타일은 대부분의 코드에서 일반적으로 사용되는 수많은 const / const 반복적 인 한 줄 접근자를 피하는 데 매우 성공적입니다.