在努力实现const-correctness的同时,我经常发现自己编写的代码如此
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的解决方案,我也喜欢自己的解决方案。 :)
答案 0 :(得分:6)
另一种方法是编写一个调用该函数的模板(使用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>(); }
};
请注意,调用没有丢失性能:由于成员指针作为模板参数传递,因此可以像往常一样内联调用。
答案 1 :(得分:4)
使用以下技巧:
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); }
};
注意可以对任何const /非const函数使用唯一函数This
。
没有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; }
};
答案 2 :(得分:3)
您可以这样做:
class Bar;
class Foo {
public:
const Bar* bar() const { return getBar(); }
Bar* bar() {
return getBar();
}
private:
Bar* getBar() const {/* Actual code */ return NULL;}
};
答案 3 :(得分:2)
我个人的感觉是,如果你这么做很多,你的设计中会有一些怀疑。在我不得不做类似事情的情况下,我通常会通过可变方法来访问这个东西。
答案 4 :(得分:1)
我之前也感受到了这种痛苦 - 本质上,你试图告诉编译器constness通过bar()
“传播”。不幸的是,据我所知,没有办法自动执行此操作...您只需手动编写该函数的第二个版本。
答案 5 :(得分:1)
仅供参考 - OP发布的代码是Scott Meyers的“Effective C ++ - Third Edition”中给出的首选方法。见第3项。
答案 6 :(得分:1)
我弯下脑子后,自己想出了答案。但是,我想我可以使用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); }
};
答案 7 :(得分:0)
假设你接受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实际上是有效的。
答案 8 :(得分:-1)
你的代码暗示const bar()实际上正在创建并返回一个新的Bar,如果你正在那么多,我发现这很奇怪。
对我来说,更大的问题是成员函数中的常量仅限于引用和非指针成员。你有(非常量)指针成员的那一刻,const函数可以改变它们,即使它声称它是盒子上的常量。
如果您的Bar实例是成员变量,请考虑返回引用。
答案 9 :(得分:-1)
Bar的常量会影响你Foo的常数吗?或者你只是这样做,因为没有办法区分const Bar * vs Bar *版本?如果是后一种情况,我只需要一个bar()返回一个Bar *并完成它。无论如何,毕竟,需要一个const Bar *才能拿到Bar *就好了。别忘了,bar()方法的常量是关于Foo,而不是关于Bar。
另外,如果你同时提供const和非const Bar *并且很高兴让Foo成为非const,那么你的Foo(以及bar()方法)的常量显然不是'重要的是,你只需填写const和非const版本就可以了。在这种情况下,如果你允许非const版本,再次只提供/ only /那个,const的使用者将愉快地采用非const。只是,你有const和非const去那里的方式,看起来它对程序没有太大的影响。
如果你想让const Foo对象能够返回const和非const Bar ,那么只需要使用bar()const并返回一个非const bar 。
我不知道,我必须看到更多才能给出真正的答案。
仔细思考,哪些常量是真正连接的,以及你的程序是否同时需要const Foos和非const Foos。似乎你没有考虑到程序需要的所有可能性。
答案 10 :(得分:-2)
一张价值超过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 ++样式转换