[英]How to generate a non-const method from a const method?
在努力保持正確性的同時,我經常發現自己正在編寫這樣的代碼
class Bar;
class Foo {
public:
const Bar* bar() const { /* code that gets a Bar somewhere */ }
Bar* bar() {
return const_cast< Bar* >(
static_cast< const Foo* >(this)->bar());
}
};
對於很多方法,比如bar()
。 編寫這些非const方法,手動調用常量方法是乏味的; 此外,我覺得我在重復自己 - 這讓我心疼。
我該怎么做才能減輕這個任務? (不允許使用宏和代碼生成器。)
編輯:除了litb的解決方案,我也喜歡我自己的解決方案。 :)
另一種方法是編寫一個調用函數的模板(使用CRTP)並從中繼承。
template<typename D>
struct const_forward {
protected:
// forbid deletion through a base-class ptr
~const_forward() { }
template<typename R, R const*(D::*pf)()const>
R *use_const() {
return const_cast<R *>( (static_cast<D const*>(this)->*pf)() );
}
template<typename R, R const&(D::*pf)()const>
R &use_const() {
return const_cast<R &>( (static_cast<D const*>(this)->*pf)() );
}
};
class Bar;
class Foo : public const_forward<Foo> {
public:
const Bar* bar() const { /* code that gets a Bar somewhere */ }
Bar* bar() { return use_const<Bar, &Foo::bar>(); }
};
請注意,調用沒有丟失性能:由於成員指針作為模板參數傳遞,因此可以像往常一樣內聯調用。
使用以下技巧:
class Bar;
class Foo {
public:
Bar* bar() {
// non-const case
/* code that does something */
}
const Bar* bar() const {
return This().bar(); // use non-const case
}
private:
//trick: const method returns non-const reference
Foo & This() const { return const_cast<Foo &>(*this); }
};
注意,可以使用獨特的功能, This
對於任何常量/非const功能。
沒有static_cast的替代解決方案(但我更喜歡第一個):
class Bar;
class Foo {
public:
const Bar* bar() const { /* code that does something */ }
Bar* bar() { return const_cast<Bar*>(cthis().bar()); } // use const case
private:
const Foo & cthis() const { return *this; }
};
你可以這樣做:
class Bar;
class Foo {
public:
const Bar* bar() const { return getBar(); }
Bar* bar() {
return getBar();
}
private:
Bar* getBar() const {/* Actual code */ return NULL;}
};
我個人的感覺是,如果你這么做,你的設計中有一些可疑的東西。 在我不得不做類似的事情的情況下,我通常通過方法可變來訪問這個東西。
我之前也感受到了這種痛苦 - 本質上,你試圖通過bar()
告訴編譯器constness“傳播”。 不幸的是,據我所知,沒有辦法自動執行此操作...您只需手動編寫函數的第二個版本。
僅供參考 - OP發布的代碼是Scott Meyers的“Effective C ++ - Third Edition”中的首選方法。 見第3項。
我彎下腦子后,自己想出了答案。 但是,我想我可以使用litb的回答中的想法來改進它,我將在稍后發布。 所以到目前為止,我的解決方案如下:
class ConstOverloadAdapter {
protected:
// methods returning pointers
template<
typename R,
typename T >
R* ConstOverload(
const R* (T::*mf)(void) const) {
return const_cast< R* >(
(static_cast< const T* >(this)->*mf)());
}
// and similar templates for methods with parameters
// and/or returning references or void
};
class Bar;
class Foo : public ConstOverloadAdapter {
public:
const Bar* bar() const {
/* implementation */ }
Bar* bar(void* = 0) { // the dummy void* is only needed for msvc
// since it cannot distinguish method overloads
// based on cv-type. Not needed for gcc or comeau.
return ConstOverload(&Foo::bar); }
};
假設你接受const-correctness作為一種技術,那么我認為這意味着你更喜歡編譯器檢查的const-correctness以簡潔。 所以你希望編譯器檢查兩件事:
如果const版本調用非const,則不會得到(2)。 如果非const版本調用const one並且const_casts結果,那么你不會得到(1)。 例如,假設Bar
實際上是char
,並且您編寫的代碼最終返回(在某些情況下)字符串文字。 這將編譯(並且-Wwrite-strings不會發出警告),但是調用者最終會得到一個指向字符串文字的非const指針。 這與“您更喜歡編譯器檢查的const-correctness”相矛盾。
如果他們都調用輔助成員函數Bar *getBar() const
,那么你得到(1)和(2)。 但是如果可以編寫那個輔助函數,那么為什么你首先要搞亂const和非const版本,那么修改const Foo返回的Bar是完全可以的? 有時候,實現的一些細節可能意味着你實現了一個帶有兩個訪問器的接口,即使你只需要一個。 否則無法寫入幫助程序,或者只能由單個幫助程序替換這兩個函數。
只要代碼大小不是問題,我認為實現(1)和(2)的最佳方法是讓編譯器實際考慮兩種情況:
struct Bar { int a; };
struct Foo {
Bar *bar() { return getBar<Bar>(this); }
const Bar *bar() const { return getBar<const Bar>(this); }
Bar *bar2() const { return getBar<Bar>(this); } // doesn't compile. Good.
Bar *bar3() const { return getBar<const Bar>(this); } // likewise
private:
template <typename B, typename F>
static B *getBar(F *self) {
// non-trivial code, which can safely call other functions with
// const/non-const overloads, and we don't have to manually figure out
// whether it's safe to const_cast the result.
return &self->myBar;
}
Bar myBar;
};
如果代碼是微不足道的,就像訪問對象擁有的某個數組的operator[]
那么我只是復制代碼。 在某些時候,上面的函數模板比復制更少編碼工作,此時使用模板。
我認為const_cast方法雖然聰明且看似標准,但是沒有用,因為它選擇了簡潔性而不是編譯器檢查的const-correctness。 如果方法中的代碼很簡單,那么您可以復制它。 如果它不是微不足道的,那么對於您或代碼維護者來說,看到const_cast實際上是有效的並不容易。
你的代碼暗示const bar()實際上正在創建並返回一個新的Bar,如果你做了那么多,我發現這很奇怪。
對我來說,更大的問題是成員函數中的常量僅限於引用和非指針成員。 你擁有(非常量)指針成員的那一刻,const函數可以改變它們,即使它聲稱是盒子上的常量。
如果您的Bar實例是成員變量,請考慮返回引用。
Bar的常數是否會影響你的Foo的常數? 或者你只是這樣做,因為沒有辦法區分const Bar * vs Bar *版本? 如果是后一種情況,我只需要一個bar()返回一個Bar *並完成它。 無論如何,畢竟,需要一個const Bar *才能拿到Bar *就好了。 別忘了,bar()方法的常量是關於Foo,而不是關於Bar。
另外,如果你同時提供常量和非常量的Bar *並且很高興讓Foo成為非常量的,那么你的Foo的常量(以及bar()方法)顯然並不那么重要,你只需填寫const和非const版本就可以了。 在這種情況下,如果你允許非const版本,再次只提供/ only /那個,const的使用者將愉快地采用非const。 只是,你有const和非const去那里的方式,看起來它對程序沒有太大的影響。
如果你想讓const Foo對象能夠返回const和非const的s,那么只需要使用bar()const並返回一個非const的Bar 。
我不知道,我必須看到更多才能給出真正的答案。
仔細思考,哪些常量是真正連接的,以及你的程序是否需要const Foos和非const Foos。 似乎你沒有考慮到程序需要的所有可能性。
一張圖片價值超過1萬字,所以:
const Item*
Storage::SearchItemBySomething( const Something &something ) const
{
const Item* pData = NULL;
// Algorythm for searching.
return pData;
}
Item*
Storage::SearchItemBySomething( const Something &something )
{
return (Item*) ((const Storage*)this)->_SearchItemBySomething(something);
}
從實際代碼復制和更改。 一般的想法是你實現你的const方法,你編寫使用第一個方法的非const方法。 你需要將“this”指針強制轉換為“const”,你需要從返回的“const Item *”中拋棄“const”。
您可以將C樣式轉換替換為C ++樣式轉換
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.