“参考限定词的正确性”或者非常量方法是否应该适用于右值?

时间:2013-06-06 22:05:42

标签: c++ c++11 const

现在GCC 4.8.1和Clang 2.9及更高版本支持它们,参考限定符(也称为"rvalue references for *this")已经变得更加广泛可用。它们允许类的行为更像内置类型,例如,禁止赋值给rvalues(否则会导致rvalue的不需要的转换为左值):

class A
{
    // ...

public:
    A& operator=(A const& o) &
    {
        // ...
        return *this;
    }
};

通常,调用右值的const成员函数是明智的,因此左值引用限定符将不合适(除非rvalue限定符可用于优化,例如将成员移出一个类而不是返回副本。)

另一方面,诸如pre减量/增量运算符之类的变量运算符应该是左值限定的,因为它们通常返回对象的左值引用。因此还有一个问题:除了概念const方法之外,是否有任何理由允许在rvalue引用上调用变异/非const方法(包括运算符),这些方法仅未标记为const因为const-correctness(包括使用内部缓存时mutable的正确应用,which may include ensuring certain thread-saftey guarantees now)在代码库中被忽略了?

为了澄清,我并不是建议在语言层面禁止rvalues上的变异方法(至少这可能会破坏遗留代码)但我相信默认(作为成语/编码风格)只允许左值变异方法通常会导致更清洁,更安全的API。但是,我感兴趣的是不这样做的示例会导致更清晰的less astonishing API。

4 个答案:

答案 0 :(得分:3)

如果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值,可以构造一个对象,将其传递给一些mutator,并使用表达式的结果来完成某些任务,而不必将其存储在L值中,即使变异者是成员函数。

另请注意,并非所有变异运算符都返回对self的引用。用户定义的mutator可以实现他们需要或想要的任何签名。 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 :(得分:0)

我认为,在检索某些值的唯一方法是改变另一个值的情况下会出现这种情况。例如,迭代器不提供“+1”或“下一步”方法。所以假设我正在为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));,但我没有看到任何关于为什么那些更清洁或更快的论据。我认为这种实现对我来说是最自然的。

答案 2 :(得分:0)

FWIW HIC ++与任务操作员一致同意:

http://www.codingstandard.com/rule/12-5-7-declare-assignment-operators-with-the-ref-qualifier/


  

非const方法是否应用于rvalues?

这个问题困扰着我。对我来说一个更明智的问题是:

  

const方法是否应该专门适用于rvalues?

我认为答案是否定的。我无法想象你想要在const rvalue *this上重载的情况,正如我无法想象你想要在const rvalue参数上重载的情况。

你在rvalues上重载,因为当你知道你可以窃取他们的内心时,它可以更有效地处理它们,但是你无法窃取const对象的内容。

*this有四种可能的重载方法:

struct foo {
    void bar() &;
    void bar() &&;
    void bar() const &;
    void bar() const &&;
};

后两个重载的常量意味着任何一个都不能改变*this,因此允许const &重载到*this和什么之间没有区别允许对const &&执行*this重载。如果没有const &&重载,const &无论如何都将绑定到左值和右值。

鉴于const &&上的重载是没用的,只是为了完整性而提供的(证明我错了!)我们只剩下一个用于ref-qualifiers的用例:在非const rvalue {{1}上重载}。可以为*this重载定义一个函数体,或者可以&&一个函数体(如果只提供= delete重载,则会隐式发生这种情况)。我可以想象很多情况下定义一个&函数体可能是有用的。

通过重载&&和一元operator->来实现指针语义的代理对象,例如boost::detail::operator_arrow_dispatch,可能会发现在operator*上使用ref-qualifiers很有用:

operator*

如果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可以通过移动而不是通过复制返回。

答案 3 :(得分:-1)

我可以想象从实际对象移动到参数的函数。