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

Инициализация класса с помощью {* this}

Было предложено членом команды, что с использованием такого типа:

return Demo{ *this };

был лучше, чем:

return Demo(*this);

Предполагая простой класс следующим образом:

class Demo {
public:
    int value1;
    Demo(){}
    Demo(Demo& demo) {
        this->value1 = demo.value1;
    }
    Demo Clone() {
        return Demo{ *this };
    }
};

Я признаю, что раньше не видел синтаксис { *this }, и не смог найти ссылку, которая объяснила это достаточно хорошо, и я понял, как эти два параметра отличаются. Есть ли преимущество в производительности, выбор синтаксиса или что-то еще?

4b9b3361

Ответ 1

У вашего коллеги отсутствует трюк с "равномерной инициализацией", нет необходимости в имени типа, когда оно известно. Например. при создании возвращаемого значения. Clone можно определить как:

Demo Clone() {
    return {*this};
}

Это вызовет конструктор копии Demo по мере необходимости. Думаете ли вы, что это лучше или нет, зависит от вас.

В GOTW 1 Саттер заявляет в качестве ориентира:

Guideline: Предпочитает использовать инициализацию с {}, например, вектор v = {1, 2, 3, 4}; или auto v = vector {1, 2, 3, 4}; потому что он более последователен, более корректен и позволяет вообще не знать о ловушках старого стиля. В случаях с одним аргументом, когда вы предпочитаете видеть только знак =, такой как int я = 42; и auto x = что угодно; опускание брекетов в порядке....

В частности, использование фигурных скобок позволяет избежать путаницы с:

Demo d();      //function declaration, but looks like it might construct a Demo
Demo d{};      //constructs a Demo, as you'd expect

Синтаксис привязки будет использовать конструктор, который сначала берет список инициализаторов, если таковой существует. В противном случае он будет использовать обычный конструктор. Это также предотвращает вероятность того, что вышеперечисленный синтаксический анализ будет указан выше.

При использовании инициализации копирования существует и другое поведение. Стандартным способом

Demo d = x;

В случае необходимости компилятор имеет возможность конвертировать x в Demo, а затем переместить/скопировать преобразованное значение r в w. Нечто похожее на Demo d(Demo(x)); означает, что вызывается более одного конструктора.

Demo d = {x};

Это эквивалентно Demo d{x} и гарантирует, что будет вызываться только один конструктор. Нельзя использовать оба назначения выше явных конструкторов.

Как упоминалось в комментариях, есть некоторые подводные камни. С классами, которые принимают initializer_list и имеют "нормальные" конструкторы, могут вызвать путаницу.

vector<int> v{5};       // vector containing one element of '5'
vector<int> v(5);       // vector containing five elements.

Ответ 2

Это просто еще один синтаксис для вызова вашего конструктора копий (на самом деле, для вызова конструктора, использующего то, что находится в фигурных скобках в качестве параметров, в данном случае - ваш конструктор копирования).

Лично я бы сказал это хуже, чем раньше, просто потому, что он делает то же самое... он просто полагается на С++ 11. Таким образом, он добавляет зависимости без преимуществ. Но ваш пробег может отличаться. Вам придется спросить своего коллегу.

Ответ 3

Я должен признать, что раньше этого не видел.

WikiPedia говорит об списках инициализации С++ 11 (поиск "Унифицированная инициализация" ):

С++ 03 имеет ряд проблем с инициализацией типов. Существует несколько способов инициализации типов, и они не все производят одинаковые результаты при обмене. Например, традиционный синтаксис конструктора может выглядеть как объявление функции, и должны быть предприняты шаги, чтобы гарантировать, что компилятор, наиболее досадный правило синтаксического анализа, не допустит его для этого. Только агрегаты и типы POD могут быть инициализированы с помощью агрегатных инициализаторов (с использованием SomeType var = {/stuff/};).

Затем, позже, у них есть этот пример,

BasicStruct var1{5, 3.2}; // C type struct, containing only POD
AltStruct var2{2, 4.3};   // C++ class, with constructors, not 
                          // necessarily POD members

со следующим объяснением:

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

Они также имеют дополнительные примеры для случая, когда были созданы конструкторы списка инициализаторов.

Таким образом, основываясь только на вышесказанном: для случая структур с открытым старым-данным я не знаю, есть ли какие-либо преимущества. Для класса С++ 11 использование синтаксиса {} может помочь избежать этих неприятных сценариев, когда компилятор считает, что вы объявляете функцию. Возможно, это преимущество, о котором говорил ваш коллега?

Ответ 4

Извините за позднюю публикацию этого обсуждения, но я хочу добавить некоторые моменты о различных типах инициализации, не упомянутых другими.

Рассмотрим:

struct foo {
  foo(int) {}
};

foo f() {
  // Suppose we have either:
  //return 1;      // #1
  //return {1};    // #2
  //return foo(1); // #3
  //return foo{1}; // #4
}

Затем

#1, #3 и #4 могут вызывать конструктор copy/move (если RVO не выполняется), тогда как #2 не будет вызывать конструктор copy/move.

Обратите внимание, что наиболее популярные компиляторы выполняют RVO, и на практике все приведенные выше выражения return эквивалентны. Однако даже когда выполняется RVO, должен быть доступен конструктор копирования/перемещения (должен быть доступен f и определен, но не удален) для #1, #3 и #4, иначе компилятор/компоновщик поднимет ошибка.

Предположим теперь, что конструктор явный:

struct foo {
  explicit foo(int) {}
};

Затем

#1 и #2 не компилируются, тогда как #3 и #4 компилируются.

Наконец, если конструктор явный и конструктор copy/move не доступен:

struct foo {
  explicit foo(int) {}
  foo(const foo&) = delete;
};

ни один из операторов return compile/link.

Ответ 5

Это называется list-initialization. Идея заключается в том, что в С++ 11 вы будете иметь равномерную инициализацию по всей доске и избегаете двусмысленности, когда компилятор может подумать, что вы можете делать объявление функции (также известное как досадный синтаксический анализ). Небольшой пример:

vec3 GetValue()
{
  return {x, y, z}; // normally vec(x, y, z)
}

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