我对以下代码感到困惑,其中类Baz
通过代理类Bar
访问其内部数据:
struct Foo{};
template<class T>
struct Bar
{
Bar(T &val_): val(val_) {}
T &val;
};
struct Baz
{
Bar<const Foo> get() const {return Bar<const Foo>(foo);}
Bar<Foo> get() {return Bar<Foo> (foo);}
Foo foo;
};
struct FooBaz
{
FooBaz(Baz &baz_): baz(baz_) {}
// Bar<const Foo> get() const {return baz.get();} // this do not compile
Bar<const Foo> get() const {return const_cast<const Baz &>(baz).get();} // the const_cast seems to be required
Baz &baz;
};
看来我必须做一个const_cast
来调度到正确的Baz::get()
功能。
我不明白的第一件事是,如果FooBaz::baz
是普通Baz
而不是引用,它就可以工作:
struct Foo{};
template<class T>
struct Bar
{
Bar(T &val_): val(val_) {}
T &val;
};
struct Baz
{
Bar<const Foo> get() const {return Bar<const Foo>(foo);}
Bar<Foo> get() {return Bar<Foo> (foo);}
Foo foo;
};
struct FooBaz
{
FooBaz(Baz &baz_): baz(baz_) {}
Bar<const Foo> get() const {return baz.get();} // Ok
Baz baz;
};
我对 const 函数成员的理解是,它使this
指向常量,但实际上我并不完全清楚它的含义......是否所有数据成员都"好像它们是常量"?有没有明确的参考?
另一个有趣的事情是,如果我使用std::reference_wrapper
作为代理类,那么它无需const_cast
即可工作:
#include <functional>
struct Foo {};
struct Baz
{
std::reference_wrapper<const Foo> get() const {return std::cref(foo);}
std::reference_wrapper<Foo> get() {return std::ref (foo);}
Foo foo;
};
struct FooBaz
{
FooBaz(Baz &baz_): baz(baz_) {}
std::reference_wrapper<const Foo> get() const {return baz.get();} // Ok
Baz &baz;
};
std::reference_wrapper
有什么魔力?
谢谢!
如果
FooBaz::baz
是普通Baz
而不是引用,则它可以工作:
这就是重点。在const
成员函数中,数据成员也被视为const
。请注意,对于引用,这意味着引用成员本身被视为const
,而不是被引用的对象。即baz
将被视为Baz & const
(常量引用)而不是Baz const &
(引用常量)。事实上,引用不能被常量限定,并且恒量性被忽略,因为即使在const
成员函数中,baz.get();
总是会调用非常量Baz::get()
。
对于std::reference_wrapper
版本,它不会改变,baz.get();
仍然调用非常量Baz::get()
并返回std::reference_wrapper<Foo>
。std::reference_wrapper
有一个转换构造函数,返回的std::reference_wrapper<Foo>
可以转换为std::reference_wrapper<const Foo>
,它存储对从std::reference_wrapper<Foo>
获得的对象的引用(通过 转换运算符)。
我必须做一个
const_cast
来调度到正确的Baz::get()
函数。
是的。或std::as_const
,或使Bar<T>
可转换为Bar<const T>
。
我对 const 函数成员的理解是,它使这个指针指向常量,但实际上我并不完全清楚它的含义......是否所有数据成员都"好像它们是常量"?
是的,成员的行为就好像他们const
表达式
E1->E2
与内置类型的表达式(*E1).E2
完全相同;这就是为什么以下规则仅处理E1.E2
的原因。在表达式
E1.E2
中:
如果
E2
是非静态数据成员:如果
E2
的引用类型为T&
或T&&
,则结果是T
类型的左值,用于指定 E2 引用的对象或函数, 否则,如果E1
是左值,则结果是一个左值,指定E1
的非静态数据成员, 否则(如果E1
是一个 x值(可以从 prvalue 具体化)),结果是一个 xvalue 指定E1
的非静态数据成员。如果
E2
不是可变成员,则结果的 cv 限定是E1
和E2
的 cv 资格的并集,否则(如果E2
是一个可变成员),它是易失性限定的并集E1
和E2
;— CPP 偏好
当通过指向const
类的指针进行访问时,其成员将变为const
。
但恒常性仅在顶层添加。如果你通过指向const
的指针访问int *member;
,它会变成int *const member;
,而不是const int *const member;
。
引用的工作方式类似,但由于引用本身不能const
1,因此它们的类型不会更改。
如果我使用
std::reference_wrapper
作为代理类,那么它无需const_cast
那是因为它有一个构造函数,允许从std::reference_wrapper<T>
构造std::reference_wrapper<const T>
。
1是的,引用在创建后无法更改为指向其他对象,但形式上它不是const
。std::is_const
返回引用的false
。