В чем смысл присвоения возвращаемому значению геттера, принимающего этот указатель по значению? - PullRequest
0 голосов
/ 05 августа 2020

Это сомнение пришло ко мне, когда я перешел на существующий код и по ошибке использовал геттер для установки свойства,

obj.getProp() = otherProp;

вместо вызова сеттера,

obj.setProp(otherProp);

I не осознал ошибку, потому что не было ошибки при компиляции или во время выполнения; назначение привело к отсутствию операции.

Итак, я придумал следующий пример, который выводит 337:

#include <iostream>

struct A {
    int x = 0;
    A(int x) : x(x) {}
    A(A& a) : x(a.x) {}
    void operator=(A const& other) { x = other.x; }
};

struct B {
    A a{3};
    int x{3};
    A  getAbyVal() { return a; }
    A& getAbyRef() { return a; }
    int getXbyVal() { return x; }
};

int main() {
    B b;
    std::cout << b.a.x;   // this and the other two cout print what I expect, but...
    b.getAbyVal() = A{7}; // ... I expected this to fail at compilation time in the first place...
    //b.getXbyVal() = 3;  // ... just like this fails.
    std::cout << b.a.x;
    b.getAbyRef() = A{7};
    std::cout << b.a.x;

}

Итак, мой вопрос состоит из двух складок:

  • чем в b.getAbyVal() = A{7}; отличается от b.getXbyVal() = 3;, так что первый компилируется, а второй нет (помимо того, что типы A и int)?
  • изменение void operator=(A const& other) { x = other.x; } на void operator=(A const& other) & { x = other.x; } приводит к невозможности компиляции b.getAbyVal() = A{7};. Почему это так?

Ответы [ 2 ]

1 голос
/ 05 августа 2020

что в b.getAbyVal () = A {7}; отличается от b.getXbyVal () = 3; так что первый компилируется, а второй нет (кроме того, что это типы A и int)?

Удивительно, но разница в типах - это именно то, что заставляет один компилировать правильно, а другой - Ошибка.

A имеет оператор присваивания, определенный для него, поэтому компилятор послушно вызывает его для возвращаемого значения (только чтобы позже отбросить весь объект). Но написанный вами код поддерживает это. С точки зрения компилятора, в вашем операторе присваивания могли произойти некоторые другие интересные вещи, несмотря на то, что объект будет удален ( побочные эффекты на формальном языке).

С int в качестве возвращаемого значения компилятор знает , что нет никаких побочных эффектов при присвоении значения int, поэтому присвоение любого значения объекту, который должен быть немедленно удален, не имеет никакого смысла.

1 голос
/ 05 августа 2020

чем в b.getAbyVal() = A{7}; отличается от b.getXbyVal() = 3;, так что первый компилируется, а второй - нет (помимо того, что типы A и int)?

Разница как раз в том, что одна функция возвращает тип класса, а другая функция возвращает тип POD. Временный int, например, не может быть назначен на:

42 = x; // error

, поэтому аналогично язык запрещает назначение временному int, возвращаемому из функции. Это не поведение по умолчанию для определяемых пользователем типов классов, поэтому присвоение временному A компилирует:

A{} = x; // ok

изменение void operator=(A const& other) { x = other.x; } на void operator=(A const& other) & { x = other.x; } приводит к тому, что b.getAbyVal() = A{7}; не компилируется. Почему это так?

Добавление & в конце называется квалификатором ссылки и позволяет определяемому пользователем классу иметь ту же семантику, что и тип POD, когда он появляется. назначить на временный. Добавление & в конце operator= ограничивает его использование только для l-значения (в основном, именованной переменной или ссылки, возвращаемой функцией).

A{} = x;  // now error
A a;
a = x;    // still ok
...