Để tránh trùng lặp liên quan đến C ++ không tầm thường, có trường hợp const_cast sẽ hoạt động nhưng chức năng const riêng không trả về không const?
Trong mục 3 C ++ hiệu quả của Scott Meyers , ông gợi ý rằng const_cast kết hợp với một diễn viên tĩnh có thể là một cách hiệu quả và an toàn để tránh mã trùng lặp, ví dụ:
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 tiếp tục giải thích rằng có chức năng const gọi hàm non-const là nguy hiểm.
Mã dưới đây là một ví dụ phản ánh hiển thị:
- trái với đề xuất của Meyers, đôi khi const_cast kết hợp với dàn diễn viên tĩnh lại nguy hiểm
- đôi khi có chức năng const gọi non-const ít nguy hiểm hơn
- đôi khi cả hai cách sử dụng const_cast đều ẩn các lỗi trình biên dịch hữu ích
- tránh một const_cast và có thêm một thành viên riêng tư trả về một không phải const là một lựa chọn khác
Các chiến lược const_cast của việc tránh sao chép mã có được coi là thông lệ tốt không? Bạn có muốn chiến lược phương pháp riêng thay thế? Có trường hợp const_cast sẽ hoạt động nhưng một phương thức riêng tư sẽ không? Có những lựa chọn khác (ngoài việc sao chép)?
Mối quan tâm của tôi với các chiến lược const_cast là ngay cả khi mã chính xác khi được viết, sau này trong quá trình bảo trì, mã có thể trở nên không chính xác và const_cast sẽ ẩn một lỗi trình biên dịch hữu ích. Có vẻ như một chức năng riêng tư phổ biến thường an toàn hơn.
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 {};