我们不能对rvalue使用预增量:
int i = 0;
int j = ++i++; // Compile error: lvalue required
如果我们定义一个类:
class A
{
public:
A & operator++()
{
return *this;
}
A operator++(int)
{
A temp(*this);
return temp;
}
};
然后我们可以编译:
A i;
A j = ++i++;
A对象和int数据类型之间的区别是什么
j = ++i++;
用A编译但不使用int编译?
最佳答案
发生这种情况的原因是,当将重载运算符定义为成员函数时,它们遵循某些语义,这些语义与调用成员函数更相关,而与内置运算符的行为无关。请注意,默认情况下,如果我们声明一个非静态成员函数,例如:
class X {
public:
void f();
X g();
};
那么我们就可以在左值和右值类类型表达式上调用它:
X().f(); // okay, the X object is prvalue
X x;
x.f(); // okay, the X object is lvalue
x.g().f(); // also okay, x.g() is prvalue
当运算符表达式的重载解析选择一个成员函数时,该表达式将更改为仅对该成员函数的调用,因此它遵循相同的规则:
++A(); // okay, transformed to A().operator++(), called on prvalue
A a;
++a; // okay, transformed to a.operator++(), called on lvalue
++a++; // also technically okay, transformed to a.operator++(0).operator++(),
// a.operator++(0) is a prvalue.
内在运算符和重载运算符之间的这种不等价也会在赋值的左子表达式中发生:无意义的语句
std::string() = std::string();
是合法的,但是语句int() = int();
是不合法的。但是您在注释中指出“我想设计一个防止
++a++
的类”。至少有两种方法可以做到这一点。首先,您可以使用非成员运算符代替成员。大多数重载运算符可以实现为成员或非成员,其中需要将类类型添加为非成员函数的附加第一参数类型。例如,如果
a
具有类类型,则表达式++a
会尝试查找一个函数,就好像它是a.operator++()
一样,并且还要查找一个函数,就好像它是operator++(a)
一样;并且a++
表达式将查找表达式a.operator++(0)
或operator++(a, 0)
的函数。(这种尝试两种方式的模式不适用于名为
operator=
,operator()
,operator[]
或operator->
的函数,因为它们只能被定义为非静态成员函数,而不能被定义为非成员。名为operator new
,operator new[]
,operator delete
或的函数operator delete[]
以及用户定义的文字函数(其名称以operator ""
开头)遵循完全不同的规则集。)当类参数与实函数参数而不是非静态成员函数的“隐式对象参数”匹配时,参数中使用的引用类型(如果有的话)将照常控制参数是否可以为左值,右值,或两者之一。
class B {
public:
// Both increment operators are valid only on lvalues.
friend B& operator++(B& b) {
// Some internal increment logic.
return b;
}
friend B operator++(B& b, int) {
B temp(b);
++temp;
return temp;
}
};
void test_B() {
++B(); // Error: Tried operator++(B()), can't pass
// rvalue B() to B& parameter
B b;
++b; // Okay: Transformed to operator++(b), b is lvalue
++b++; // Error: Tried operator++(operator++(b,0)), but
// operator++(b,0) is prvalue and can't pass to B& parameter
}
另一种方法是向成员函数添加ref限定符,这些限定符已添加到C++ 11版本的语言中,作为控制成员函数的隐式对象参数必须为左值还是右值的特定方式:
class C {
public:
C& operator++() & {
// Some internal increment logic.
return *this;
}
C operator++(int) & {
C temp(*this);
++temp;
return temp;
}
};
请注意,参数列表与正文开头之间的
&
。这将函数限制为仅接受C
类型的左值(或将其隐式转换为C&
引用的值)作为隐式对象参数,这与在同一位置的const
允许隐式对象参数的类型为const C
相似。如果您想要一个函数要求一个左值,但允许该左值可选地为const
,则const
在ref限定词之前:void f() const &;
void test_C() {
++C(); // Error: Tried C().operator++(), doesn't allow rvalue C()
// as implicit object parameter
C c;
++c; // Okay: Transformed to c.operator++(), c is lvalue
++c++; // Error: Tried c.operator++(0).operator++(), but
// c.operator++(0) is prvalue, not allowed as implicit object
// parameter of operator++().
}
为了使
operator=
像标量类型那样发挥作用,我们不能使用非成员函数,因为该语言仅允许成员operator=
声明,但是ref限定符同样可以工作。您甚至可以使用= default;
语法让编译器生成主体,即使函数的声明方式与隐式声明的赋值函数的方式完全不同。class D {
public:
D() = default;
D(const D&) = default;
D(D&&) = default;
D& operator=(const D&) & = default;
D& operator=(D&&) & = default;
};
void test_D() {
D() = D(); // Error: implicit object argument (left-hand side) must
// be an lvalue
}
关于C++后递增: objects vs primitive types,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/54777440/