"Reference qualifier correctness"还是应该将非常量方法应用于右值?
"Reference qualifier correctness" or should a non-const method ever apply to rvalues?
现在GCC 4.8.1和Clang 2.9及更高版本支持它们,引用限定符(也称为" *this的右值引用")已经变得更加广泛可用。它们允许类的行为更像内置类型,例如,不允许对右值赋值(否则会导致不必要的将右值强制转换为左值):
class A
{
// ...
public:
A& operator=(A const& o) &
{
// ...
return *this;
}
};
一般来说,调用右值的const
成员函数是明智的,因此左值引用限定符将不合适(除非右值限定符可用于优化,例如将成员移出类而不是返回副本)。
const
方法(包括操作符)在右值引用上调用,除了概念上的const
方法,这些方法仅未标记为const
,因为常量正确性(包括使用内部缓存时正确应用mutable
,现在可能包括确保某些线程安全保证)在代码库中被忽略?澄清一下,我并不是建议禁止在语言级别上修改左值的方法(至少这可能会破坏遗留代码),但我相信默认(作为一种习惯用法/编码风格)只允许左值修改方法通常会导致更干净、更安全的api。然而,我对那些不这样做会导致更干净、更少令人惊讶的api的例子更感兴趣。
如果r值用于完成某些任务,则对r值进行操作的mutator是有用的,但在此期间它保持某种状态。例如:
struct StringFormatter {
StringFormatter &addString(string const &) &;
StringFormatter &&addString(string const &) &&;
StringFormatter &addNumber(int) &;
StringFormatter &&addNumber(int) &&;
string finish() &;
string finish() &&;
};
int main() {
string message = StringFormatter()
.addString("The answer is: ")
.addNumber(42)
.finish();
cout << message << endl;
}
通过允许l值或r值,可以构造一个对象,将其传递给一些变量,并使用表达式的结果来完成某些任务,而不必将其存储在l值中,即使这些变量是成员函数。
还要注意,并非所有的变异操作符都返回对self的引用。用户定义的突变器可以实现他们需要或想要的任何签名。mutator可以消耗对象的状态以返回更有用的东西,并且通过作用于r值,对象被消耗的事实不是问题,因为否则状态将被丢弃。实际上,使用对象状态以产生其他有用内容的成员函数必须这样标记,以便更容易看到l值何时被使用。例如:
MagicBuilder mbuilder("foo", "bar");
// Shouldn't compile (because it silently consumes mbuilder's state):
// MagicThing thing = mbuilder.construct();
// Good (the consumption of mbuilder is explicit):
MagicThing thing = move(mbuilder).construct();
我认为它出现在检索某些值的唯一方法是通过改变另一个值的情况下。例如,迭代器不提供"+1"或"next"方法。因此,假设我正在为stl列表迭代器构造一个包装器(可能是为我自己的列表支持的数据结构创建一个迭代器):
class my_iter{
private:
std::list::iterator<T> iter;
void assign_to_next(std::list::iterator<T>&& rhs) {
iter = std::move(++rhs);
}
};
这里,assign_to_next方法接受一个迭代器,并将这个迭代器赋值为它之后的下一个位置。不难想象这可能有用的情况,但更重要的是,这个实现没有什么令人惊讶的。没错,我们也可以说iter = std::move(rhs); ++iter;
或++(iter = std::move(rhs));
,但我看不出有任何理由说明为什么这些会更干净或更快。我认为这样的实现对我来说是最自然的。
关于赋值操作符,FWIW hc++同意您的看法:
http://www.codingstandard.com/rule/12-5-7-declare-assignment-operators-with-the-ref-qualifier/这个问题把我难住了。对我来说,更明智的问题是:非const方法应该应用于右值吗?
const方法应该只应用于右值吗?
我认为答案是否定的。我无法想象会有重载const右值*this
的情况,就像我无法想象重载const右值实参的情况一样。
你重载右值,因为当你知道你可以窃取它们的内部时,你可以更有效地处理它们,但是你不能窃取const
对象的内部。
*this
:
struct foo {
void bar() &;
void bar() &&;
void bar() const &;
void bar() const &&;
};
后两个重载的一致性意味着它们都不能改变*this
,所以const &
重载允许对*this
做的事情和const &&
重载允许对*this
做的事情之间没有区别。在没有const &&
重载的情况下,const &
无论如何都将绑定到左值和右值。
考虑到const &&
上的重载是无用的,并且只是为了完整性而提供的(证明我错了!),我们只剩下一个ref_qualifier的用例:重载非const右值*this
。可以为&&
重载定义函数体,也可以为= delete
重载定义函数体(如果只提供&
重载,则会隐式地进行此操作)。我可以想象在很多情况下,定义&&
函数体可能是有用的。
通过重载operator->
和一元operator*
(如boost::detail::operator_arrow_dispatch
)实现指针语义的代理对象可能会发现在其operator*
上使用reff限定符很有用:
template <typename T>
struct proxy {
proxy(T obj) : m_obj(std::move(obj)) {}
T const* operator->() const { return &m_obj; }
T operator*() const& { return m_obj; }
T operator*() && { return std::move(m_obj); }
private:
T m_obj;
};
如果*this
是右值,那么operator*
可以通过移动而不是复制返回。
我可以想象函数从实际对象移动到参数。
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 是否有内置方法可以强制转换为不同的基础类型,但保留常量限定符?
- 为什么常量方法可以采用非常量引用?
- 常量方法中的非常量 lambda 捕获
- 友元方法作为常量
- 一个模板方法,用于同时接受常量和非常量参数
- 使用大量已知常量变量的正确方法
- 使用迭代器替换映射中的常量项的方法
- 一种优雅或至少可行的方法,用于使用和接受具有重载方法和运算符的不同大小的文字数组常量
- 初始化不是整数的巨大常量多维数组的最佳方法是什么?
- Gmock 常量方法不调用,而是调用原始方法
- 为什么我们需要常量方法?
- 从模块导出全局常量的正确方法是什么?
- 模板常量/非常量方法
- 定义常量变量的最佳方法
- 常量字符*的模板方法专用化
- 常量静态成员函数:有另一种方法可用吗?
- 使方法常量后声明不兼容
- c++ 是否提供了一种使整个结构常量(不可修改)的方法?
- 当底层OpenGL状态被修改时,我应该声明一个方法常量吗