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

Существуют ли какие-либо последствия использования * этого для инициализации класса?

В маленькой игре, которую я пишу, у меня есть класс Weapon с двумя конструкторами, один из которых принимает некоторые параметры для создания специального оружия и тот, который захватывает по умолчанию (CHAIN_GUN):

Weapon::Weapon (void) {
    // Standard weapon
    *this = getWeapon(CHAIN_GUN);
    return;
}

Вопрос: Есть ли какие-либо негативные последствия при использовании *this и operator= для инициализации класса?

4b9b3361

Ответ 1

Представьте, что кто-то попросил вас нарисовать картину... вы бы,

  • сначала нарисуйте ваш по умолчанию (1-й) (знакомый смайлик, которому так нравится),
  • затем нарисуйте то, что кто-то попросил (2-й),
  • сделать одно и то же одно еще раз, но на холсте, содержащем ваше значение по умолчанию,
  • а затем записать вторую картину?

Это сообщение попытается объяснить, почему это сравнение имеет значение.


ПОЧЕМУ ЭТО ПЛОХАЯ ИДЕЯ?

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

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

Непонятно, что при запросе построения объекта 1 мы создаем объекты 2, чтобы позже скопировать значения со 2-го на 1-й и отбросить 2-й.

Заключение: Не делайте этого.

( Примечание: в случае, когда Weapon имеет базовые классы, это будет еще хуже)

(Примечание: Другая потенциальная опасность заключается в том, что функция factory случайно использует конструктор по умолчанию, что приводит к бесконечной рекурсии, не обнаруженной во время компиляции, как отмечено @Ratchet Freat)


ПРЕДЛАГАЕМОЕ РЕШЕНИЕ

В вашем конкретном случае вам гораздо лучше использовать аргумент по умолчанию в вашем конструкторе, как в приведенном ниже примере.

class Weapon {
public:
  Weapon(WeaponType w_type = CHAIN_GUN);
  ...
}

Weapon w1;             // w_type = CHAIN_GUN
Weapon w2 (KNOWLEDGE); // the most powerful weapon

( Примечание. Альтернативой вышесказанному было бы использовать конструктор делегирования , доступный на С++ 11)

Ответ 2

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

Weapon::Weapon(GunType g = CHAIN_GUN)
: // Initialize members based on g
{
}

В других случаях вы можете использовать конструктор делегирования (с С++ 11 или новее):

Weapon::Weapon(GunType g)
: // Initialize members based on g
{
}

Weapon::Weapon()
: Weapon(CHAIN_GUN) // Delegate to other constructor
{
}

Ответ 3

Следует иметь в виду, что если operator= - или любая функция, которую он вызывает, - это virtual, то будет вызываться производный класс . Это может привести к неинициализированным полям, а затем Undefined Поведение, но все зависит от ваших членов данных.

В более общем плане, ваши базы и члены данных гарантированно инициализируются, если они имеют конструкторы или отображаются в списке инициализации (или с С++ 11 назначаются в объявлении класса) - поэтому, кроме virtual выше, operator= часто будет работать без Undefined Behavior.

Если база или элемент были инициализированы до вызова operator=(), тогда начальное значение перезаписывается до его использования в любом случае, оптимизатор может или не сможет удалить первую инициализацию. Например:

std::string s_;
Q* p_;
int i_;

X(const X& rhs)
  : p_(nullptr)  // have to initialise as operator= will delete
{
    // s_ is default initialised then assigned - optimiser may help
    // i_ not initialised but if operator= sets without reading, all good
    *this = rhs;
}

Как вы можете видеть, это немного подвержено ошибкам, и даже если вы правильно поняли, кто-то, пришедший позже, обновит operator=, может не проверить, использует ли его конструктор (ab)....

В конечном итоге вы можете обрести бесконечную рекурсию, которая приведет к переполнению стека, если getWeapon() использует шаблоны Prototype или Flyweight и пытается скопировать конструкцию Weapon, которую он возвращает.


Сделав шаг назад, возникает вопрос о том, почему getWeapon(CHAIN_GUN); существует в этой форме. Если нам нужна функция, создающая оружие на основе типа оружия, то на первый взгляд конструктор Weapon(Weapon_Type); кажется разумным. Тем не менее, есть редкие, но многочисленные случаи, когда getWeapon может возвращать нечто, отличное от объекта Weapon, которое никогда не может быть привязано к Weapon, или может быть раздельным для причин сборки/развертывания....

Ответ 4

Если вы определили оператор присваивания non-copy =, который позволяет Weapon изменять его тип после построения, то реализация конструктора с точки зрения присвоения работает очень хорошо и является хорошим способом централизации кода инициализации, Но если a Weapon не предназначено для изменения типа после построения, то оператор присваивания без копирования = не имеет смысла иметь, не говоря уже об использовании для инициализации.

Ответ 5

Я уверен, да.

Вы уже создали объект внутри своей функции getWeapon, а затем копируете его, что может привести к длительной работе. Итак, по крайней мере, вам нужно попробовать переместить семантику.

Но. Если внутри "getWeapon" вы вызываете конструктора (и вы так или иначе "getWeapon" должны создать свой класс, чтобы вернуть его в вашу операцию копирования), вы создаете очень нечеткую архитектуру, когда один конструктор вызывает функцию, которая вызывает другой конструктор.

Я считаю, что вам нужно отделить инициализацию параметров до частных функций, которые должны быть вызваны из ваших конструкторов так, как вы хотите.