Подтвердить что ты не робот

С++ {* this} внутри фигурных скобок

Следующий код компилируется отлично:

g++ -std=c++11 test.cpp -Wall -Wextra -Wfatal-errors && ./a.out

Однако, если я удалю фигурные фигурные скобки из {*this} и вместо этого использую *this, я столкнусь с ошибкой:

ошибка: использование удаленной функции 'Obj:: Position:: Position (Obj:: Position & &)

В чем разница между {*this} и *this?

class Obj
{
    template<bool> friend class Position;

    double data;
public:
    class Position
    {
        const Obj& ref;
    public:
        inline Position(const Obj& ref): ref(ref){}
        inline Position(Position const &) = delete;
        inline Position(Position &&) = delete;
    };
    inline Obj(){}
    inline Obj(const double &data): data(data){}
    inline auto get_pos() const-> Position{return {*this};} /* <--- here */
    inline auto get_pos()-> Position{return {*this};}
};

int main()
{
    return 0;
}
4b9b3361

Ответ 1

Разница между ними действительно довольно тонкая. С++ 11 представил инициализацию списка функций (также иногда называемую инициализацией скобок):

Прежде чем С++ 11, когда вы хотите построить по умолчанию-конструкцию и объект o типа Obj и построить Position p из o, вам пришлось написать

Obj o;              // default construct o
Obj::Position p(o); // construct p using Position(Obj const&)

Общей ошибкой для новичков (особенно с фоном Java) было попытаться написать это:

Obj o();            // mistake: declares a function o returning an Obj
Obj::Position p(o); // error: no constructor takes a function 

Первая строка объявляет function, а вторая пытается создать Position с помощью конструктора, который в качестве аргумента принимает указатель на функцию. Чтобы иметь одинаковый синтаксис инициализатора, С++ 11 представил инициализацию списка:

Obj oo{};             // new in C++11: default construct o of type Obj
Obj::Position p1(oo); // possible before (and after) C++11
Obj::Position p2{oo}; // new in C++11: construct p2 using Position(Obj const&)

Этот новый синтаксис также работает в return -statements, и это приводит к ответу на ваш вопрос: разница между return {*this}; и return *this; заключается в том, что первая инициализирует возвращаемое значение напрямую from *this, в то время как последний сначала преобразует *this во временный объект Position, а затем инициализирует возвращаемое значение косвенно из этого временного, что терпит неудачу, поскольку и copy-and move-constructor были явно удалены.

Как отмечали предыдущие плакаты, большинство компиляторов исключают эти временные объекты, потому что они ничем не полезны; но это возможно только в том случае, если они могут быть использованы в теории, потому что доступен либо экземпляр, либо механизм перемещения. Потому что это приводит к большой путанице (почему мне нужны скобки вокруг моего оператора return? Является ли компилятор выдавать копию или нет?), С++ 17 устраняет эти ненужные временные ряды и инициализирует возвращаемое значение непосредственно в обоих случаях (return {*this}; и return *this).

Вы можете попробовать это, используя компилятор, который поддерживает С++ 17. В clang 4.0 или gcc 7.1 вы можете передать --std=c++1z, и ваш код должен компилироваться с помощью скобок и без них.

Ответ 2

Когда фигурные скобки присутствуют, вы copy-list-initializing возвращаемое значение, не задействован конструктор копирования/перемещения. Возвращаемое значение построено на месте с помощью конструктора Position(const Obj&).

Обратите внимание, что код не сможет скомпилироваться даже с фигурными фигурными скобками, если вы создали конструктор Position(const Obj&) explicit, потому что инициализация списка копий не позволяет вызывать явные конструкторы.

Если вы опускаете фигурные скобки, то семантически временный объект Position создается внутри функции, а возвращаемое значение перемещается из этого временного. На практике большинство реализаций будут преодолевать конструкцию перемещения, но для этого все же требуется наличие конструктора жизнеспособного перемещения, что здесь не так, потому что оно было явно удалено. Именно по этой причине ваш код не будет компилироваться без брекетов.

Используя компилятор С++ 17, ваш код будет компилироваться даже без фигурных скобок из-за гарантированного копирования-разрешения.

Ответ 3

Это хороший! Это связано с тем, что return {...} означает "вернуть объект возвращаемого типа функции, инициализированный с помощью инициализатора списка...".

Инициализаторы списков описаны здесь более подробно:

http://en.cppreference.com/w/cpp/language/list%20initialization

Итак, разница в том, что {*this} вызывает это:

inline Position(const Obj& ref): ref(ref){}

В то время как *this пытается преобразовать Obj& в Position, используя явно удаленные операторы присваивания (pre С++ 11, их нужно было бы сделать private, и вы получите еще более запутанное сообщение об ошибке если будут доступны инициализаторы списка...):

inline Position(Position const &) = delete;
inline Position(Position &&) = delete;

Ответ 4

откровенно, используя ваш класс и следующий main():

int main()
{
    Obj o1;
    cout<<"get position"<<'\n';
    Obj::Position pos= o1.get_pos();


    cout.flush();
    return 0;
}

он не компилирует (gcc/mingw) в обоих случаях (-std = С++ 14), с или без фигурных скобок, и он жалуется на отсутствие конструктора Position (Position & &), который удаляется. Это разумно, потому что кажется, что в обоих случаях выполняется создание объекта временного возвращения, который затем должен быть перемещен в пункт назначения. Это невозможно, поскольку конструктор перемещения удаляется. И наоборот, используя флаг -std = С++ 17, он компилируется в обоих случаях (с или без фигурных скобок), поскольку, скорее всего, мы добиваемся гарантированной оптимизации возвращаемого значения С++ 17. Надеюсь, это поможет.