Инициализация данных элемента типа указателя значением NULL в конструкторе, использующем шаблон Builder

У меня есть конструктор, который использует внутренний объект Builder для создания экземпляра сложного объекта. Пять элементов в структуре данных относятся к типам указателей. Однако, используя этот шаблон, я сталкиваюсь с проблемами, когда объект уничтожается. Вот как выглядит мой конструктор со списком инициализации членов:

Player::Player(const Builder& builder) 
    :m_name(builder._name)
    ,m_description(builder._description)
    ,m_primaryAttributes(builder._primaryAttributes)
    ,m_abilityAttributes(builder._abilityAttributes)
    ,m_armor(builder._armor)
    ,m_weapon(builder._weapon)
    ,m_inventory(new ComponentMap())
{}

Клиентский код работает хорошо, как и ожидалось:

Player* player = Player::Builder()
    .name("Dylan")
    .description("Super bad-ass hero of the game")
    .primaryAttributes(createPrimaryAttributes())
    .abilityAttributes(createAbilityAttributes())
    .weapon(createWeapon())
    .armor(createArmor())
    .build();

Однако, если я пропущу один из аргументов в цепочке сообщений, а затем уничтожу свой объект Player, произойдет что-то плохое:

Player* player = Player::Builder()
    .name("Dylan")
    .description("Super bad-ass hero of the game")
    .primaryAttributes(createPrimaryAttributes())
    .abilityAttributes(createAbilityAttributes())
    .armor(createArmor())
    .build();

// ...

delete player;

// ...

// cleanMemory() gets called in Player::~Player()
void Player::cleanMemory()
{
    if(m_primaryAttributes != NULL )
        delete m_primaryAttributes;
    if(m_abilityAttributes != NULL )
        delete m_abilityAttributes;
    if(m_inventory != NULL )
        delete m_inventory;
    if(m_weapon != NULL)          // oops, bad stuff happens here
        delete m_weapon;
    if(m_armor != NULL)
        delete m_armor;
}

Очевидно, это происходит потому, что указатель на оружие не был инициализирован ни NULL, ни экземпляром объекта Weapon. Конструктор также не допускает значение по умолчанию NULL (по крайней мере, из того, что я вижу) в случае, если один метод Builder опущен в цепочке. На данный момент клиент должен либо передать Weapon указатель на NULL, либо экземпляр объекта.

Есть ли способ обойти это, не пересматривая полностью этот конструктор Builder? Или следует провести рефакторинг с использованием другого шаблона, такого как Factory, и просто вернуться к обычному конструктору со списком позиционных параметров?


person dtg    schedule 03.01.2013    source источник
comment
пожалуйста, не могли бы показать нам функцию Player::Builder?   -  person billz    schedule 03.01.2013
comment
Моей первой мыслью были умные указатели. Это полностью устранило бы функцию cleanMemory().   -  person drescherjm    schedule 03.01.2013
comment
Я хочу увидеть конструктор для Builder.   -  person Mooing Duck    schedule 03.01.2013
comment
@Mooing Duck и @billz: конструктор для Builder - это просто конструктор по умолчанию, но теперь, когда вы упомянули об этом, теперь я вижу, что я, вероятно, могу инициализировать там значения типа указателя. Спасибо.   -  person dtg    schedule 03.01.2013
comment
Мне очень любопытно, как возможен этот вызов, поэтому я хочу увидеть исходный код. Player* player = Player::Builder() .name("Dylan") .description("Super bad-ass hero of the game") .primaryAttributes(createPrimaryAttributes()) .abilityAttributes(createAbilityAttributes()) .weapon(createWeapon()) .armor(createArmor()) .build();   -  person billz    schedule 03.01.2013
comment
@billz: я не буду воспроизводить его выше, но он очень похож на этот шаблон Builder в этом посте: stackoverflow.com/questions/7520250/   -  person dtg    schedule 03.01.2013
comment
Просто примечание. Нет необходимости проверять NULL перед выполнением delete.   -  person Asha    schedule 03.01.2013


Ответы (1)


Пример кода, который вы указали, не очень хорошая кодовая база, я бы просто предложил ниже build pattern:

class Builder
{
   Weapon* BuildWeapon() { return new Weapon(); }
   Armor*  BuildArmor(); { return new Armor(); }    
};

class Player
{
public:
  Player(const Builder& builder) 
  : weapon_ptr(builder.BuildWeapon()),
    armer_ptr(builder.BuildArmor())

private:
  std::shared_ptr<Weapon> weapon_ptr;
  std::shared_ptr<Armor>  armor_ptr;
};   

использование:

Builder builder;
std::shared_ptr<Player> player(new Player(builder));

Или вы могли бы просто

class Player2
{
public:
  Player() {}
  void SetWeapon(Weapon* p) { weapon_ptr.reset(p); }
  void SetArmor(Armor* p) { armor_ptr.reset(p); }

private:
  std::shared_ptr<Weapon> weapon_ptr;
  std::shared_ptr<Armer>  armer_ptr;
};

использование:

   Builder builder;
   std::shared_ptr<Player> player;
   player->SetWeapon(builder.BuildWeaper());
   player->SetArmor(builder.BuildArmor());

Поскольку weapon_ptr, armer_ptr являются интеллектуальными указателями, больше нет необходимости вызывать delete для динамически выделяемой памяти, поэтому функцию cleanMemory() можно удалить.

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

person billz    schedule 03.01.2013
comment
Спасибо. В моей предыдущей реализации я использовал Builder в своем собственном классе, как вы предложили выше, вместо вложенного в класс Player. Однако мое текущее решение использует интерфейс Builder, при этом каждый сложный объект, который у меня есть, использует свой собственный вложенный Builder, который реализует интерфейс Builder. Таким образом, я могу инкапсулировать функциональность Builder в каждом классе, который ее требует, без необходимости создавать экземпляр объекта Builder каждый раз, когда я хочу создать сложный объект. Тем не менее, я буду работать с вашей идеей, используя умный указатель. - person dtg; 03.01.2013
comment
передать объект Builder в конструктор Player или нет - это минимальная проблема, это небольшое изменение стиля дизайна. - person billz; 03.01.2013