C++ post-increment:对象与原始类型

Nul*_*ull 6 c++ overloading post-increment

我们不能在rvalues上使用预增量:

int i = 0;
int j = ++i++; // Compile error: lvalue required
Run Code Online (Sandbox Code Playgroud)

如果我们定义一个类:

class A
{
public:
    A & operator++()
    {
        return *this;
    }
    A operator++(int)
    {
        A temp(*this);
        return temp;
    }
};
Run Code Online (Sandbox Code Playgroud)

然后我们可以编译:

A i;
A j = ++i++;
Run Code Online (Sandbox Code Playgroud)

A对象和int数据类型之间有什么不同

j = ++i++;
Run Code Online (Sandbox Code Playgroud)

用A编译但不用int编译?

asc*_*ler 3

发生这种情况是因为当重载运算符被定义为成员函数时,它们遵循一些与调用成员函数更相关的语义,而不是与内置运算符的行为相关。请注意,默认情况下,如果我们声明一个非静态成员函数,例如:

class X {
public:
    void f();
    X g();
};
Run Code Online (Sandbox Code Playgroud)

那么我们可以在左值和右值类类型表达式上调用它:

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
Run Code Online (Sandbox Code Playgroud)

当运算符表达式的重载决策选择成员函数时,该表达式将更改为仅对该成员函数的调用,因此它遵循相同的规则:

++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.
Run Code Online (Sandbox Code Playgroud)

内置运算符和重载运算符之间的这种不等价性也发生在赋值的左子表达式上:无意义的语句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
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是向成员函数添加引用限定符,这些限定符在 C++11 版本中添加到语言中,作为控制成员函数的隐式对象参数是否必须是左值或右值的特定方式:

class C {
public:
    C& operator++() & {
        // Some internal increment logic.
        return *this;
    }
    C operator++(int) & {
        C temp(*this);
        ++temp;
        return temp;
    }
};
Run Code Online (Sandbox Code Playgroud)

请注意&参数列表和主体开头之间的 。这限制函数仅接受类型的左值C(或隐式转换为C&引用的东西)作为隐式对象参数,类似于const同一位置的 a 如何允许隐式对象参数具有类型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++().
}
Run Code Online (Sandbox Code Playgroud)

为了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
}
Run Code Online (Sandbox Code Playgroud)