C ++后递增:对象与原始类型

时间:2019-02-20 01:24:53

标签: c++ overloading post-increment

我们不能对右值使用预增量:

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编译?

2 个答案:

答案 0 :(得分:3)

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

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();是不合法的。 / p>

但是您在注释中指出“我想设计一个防止++a++的类”。至少有两种方法可以做到这一点。

首先,您可以使用非成员运算符代替成员。大多数重载运算符可以实现为成员或非成员,其中需要将类类型添加为非成员函数的附加第一参数类型。例如,如果a具有类类型,则表达式++a将尝试查找一个函数,就像它是a.operator++()一样,以及一个函数像是operator++(a);而表达式a++将为表达式a.operator++(0)operator++(a, 0)寻找函数。

(这种尝试两种方式的模式不适用于名为operator=operator()operator[]operator->的函数,因为它们只能被定义为非静态成员函数,决不作为非成员函数,名为operator newoperator new[]operator deleteoperator delete[]的函数,以及名称以{{1 }},请遵循完全不同的规则集。)

并且当类参数与真实函数参数而不是非静态成员函数的“隐式对象参数”匹配时,参数中使用的引用类型(如果有)将照常控制参数是否可以为左值,右值或任意一个。

operator ""

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

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
}

请注意参数列表和正文开头之间的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在ref限定符之前:const

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=声明,但是ref限定符将类似地工作。甚至允许您使用operator=语法让编译器生成主体,即使函数的声明方式与隐式声明的赋值函数的方式完全不同。

= default;

答案 1 :(得分:2)

它……就是。有一些约束仅适用于原始类型而不适用于类类型(当然,您已经找到了最明显的约束!)。

很大程度上是因为内置类型的运算符是一回事,而对于类,它们只是变相的成员函数,因此是完全不同的野兽。

这令人困惑吗?我不知道;也许。

真的有令人信服的理由吗?我不知道;可能不会。基本类型有一定的惯性:为什么仅仅因为要引入类而更改C中的某些内容?允许这样做有什么好处?另一方面,禁止将其禁止用于类,对它们的实现operator++可能会做一些您作为语言设计人员没有想到的事情?