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

У меня два класса. Один позвонил Agent, другой позвонил Player. Agent — базовый класс, а Player — производный класс.

При вызове функции SetHand() из производного Player класса я сталкиваюсь с ошибкой сегмента.

#include "deck.hpp"
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <stdexcept>
#include <vector>

#ifndef BLACKJACK_INCLUDE_AGENT_H_
#define BLACKJACK_INCLUDE_AGENT_H_

class Agent {
public:
  virtual std::vector<Card> GetHand() { return hand_; }

  virtual void UpdateHand() {
    if (deck_->empty())
      throw std::runtime_error("deck is empty, cannot draw a card");

    hand_.push_back(deck_->back());
    deck_->pop_back();
  }

  void SetHand() {
    for (uint8_t i = 0; i < 1; i++) {
      if (deck_ == NULL)
        throw std::runtime_error("deck is empty, cannot draw a card");

      hand_.push_back(deck_->back());
      deck_->pop_back();
    }
  }

  virtual void UpdateDeck(std::vector<Card> deck) {
    deck_ = std::make_shared<std::vector<Card>>(deck);
  }

private:
  std::shared_ptr<std::vector<Card>> deck_;
  std::vector<Card> hand_;
};

#endif // !BLACKJACK_INCLUDE_AGENT_H_

При просмотре в отладчике это происходит потому, что он пытается использовать deck_ в Agent, который не инициализирован.

#include "agent.hpp"
#include "deck.hpp"
#include <memory>

#ifndef BLACKJACK_INCLUDE_PLAYER_H_
#define BLACKJACK_INCLUDE_PLAYER_H_

class Player : public Agent {
public:
    Player(std::vector<Card> deck, int initial_chips);

  // use negative argument to reduce chip count
  void UpdateChips(int chips);

private:
  int id_;
  bool hand_in_progress = false; 
    std::shared_ptr<std::vector<Card>> deck_;
  std::vector<Card> hand_;
  unsigned long chips_;
};

#endif // BLACKJACK_INCLUDE_PLAYER_H_

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

Код, в котором вызывается метод:

TEST(PlayerDeckTest, InitDeck) {
    Deck deck(10);

    Player player1(deck.GetDeck(), 100);

    player1.SetHand();

    std::vector<Card> hand = player1.GetHand();

    while(!hand.empty()) {
        EXPECT_FALSE(hand.back().GetSuit().empty());
        EXPECT_FALSE(hand.back().GetValue() != 0);
        hand.pop_back();
    }
}

Ре. "When calling the function SetHand() ...": нигде в показанном коде SetHand на самом деле не вызывается. Пожалуйста, отредактируйте свой вопрос, чтобы предоставить минимально воспроизводимый пример.

G.M. 17.07.2024 20:18

Пожалуйста, также покажите код, который создает объект и вызывает метод.

463035818_is_not_an_ai 17.07.2024 20:19

почему у базового и производного классов есть член с именем deck_? Это рецепт катастрофы

463035818_is_not_an_ai 17.07.2024 20:19

В Player есть два участника по имени deck_ и два участника по имени hand_. Я думаю, что на самом деле вам нужно, чтобы эти участники были только один раз в жизни Agent

463035818_is_not_an_ai 17.07.2024 20:20

У вас не может быть виртуальных или полиморфных переменных-членов.

Some programmer dude 17.07.2024 20:21

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

BoP 17.07.2024 20:26

Ваша функция SetHand() путает deck_ == NULL с deck_->empty(). В любой из этих функций deck_ может иметь значение «ноль». И когда он не «нулевой», он все равно может быть пустым.

Drew Dormann 17.07.2024 20:31

@ 463035818_is_not_an_ai удаление колоды и руки из игрока решило проблему.

inThe-FLesh 17.07.2024 21:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
8
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

У Agent и Player есть свои отдельные члены deck_ и hand_. Нет веской причины для того, чтобы Player объявлял свой собственный, вместо этого он должен использовать члены, унаследованные от Agent:

class Agent {
public:
    ...

protected: // <-- not private!
    std::shared_ptr<std::vector<Card>> deck_;
    std::vector<Card> hand_;
};
class Player : public Agent {
public:
    ...

private:
    int id_;
    bool hand_in_progress = false; 
    //std::shared_ptr<std::vector<Card>> deck_; // <- get rid of this!
    //std::vector<Card> hand_; // <- get rid of this!
    unsigned long chips_;
};

При этом не следует заставлять каждый Agent/Player помещать свою колоду std::vector внутрь std::shared_ptr. Это просто перебор. Если вы хотите использовать shared_ptr, то его следует создать внутри класса Deck, а затем передать каждому Agent/Player по мере необходимости.

Я бы предложил изменить Agent, чтобы хранить необработанный vector* указатель или vector& ссылку на исходный vector объект, нет необходимости делать отдельную копию vector для каждого Agent/Player. В каждом Agent/Player не должны быть игральные карты из своей колоды, это должны быть игральные карты из общей колоды:

class Agent {
public:
    Agent(std::vector<Card>& deck)
        : deck_(deck)
    {
    }

    std::vector<Card> GetHand() { return hand_; }

    void DrawCard() {
        if (deck_.empty())
            throw std::runtime_error("deck is empty, cannot draw a card");
        hand_.push_back(deck_.back());
        deck_.pop_back();
    }

    void SetHand() {
        hand_.clear();
        for (uint8_t i = 0; i < 2; ++i) {
            DrawCard();
        }
    }

protected:
    std::vector<Card>& deck_;
    std::vector<Card> hand_;
};
class Player : public Agent {
public:
    Player(std::vector<Card>& deck, int initial_chips)
        : Agent(deck), chips_(initial_chips)
    {
    }

    ...

private:
    int id_ = 0;
    bool hand_in_progress = false; 
    unsigned long chips_= 0;
};
TEST(PlayerDeckTest, InitDeck) {
    Deck deck(10);
    std::vector<Card> deckCards = deck.GetDeck();

    Player player1(deckCards, 100);
    player1.SetHand();

    for (auto &card : player1.GetHand()) {
        EXPECT_FALSE(card.GetSuit().empty());
        EXPECT_FALSE(card.GetValue() != 0);
    }

    Player player2(deckCards, 10);
    player2.SetHand();

    for (auto &card : player2.GetHand()) {
        EXPECT_FALSE(card.GetSuit().empty());
        EXPECT_FALSE(card.GetValue() != 0);
    }
}

Еще лучше было бы, если бы каждый Agent/Player содержал указатель/ссылку на реальный объект Deck вместо его внутреннего std::vector, например:

class Deck{
public:
    Card DrawCard() {
        if (cards_.empty())
            throw std::runtime_error("deck is empty, cannot draw a card");
        Card card = cards_.back();
        cards_.pop_back();
        return card;
    }

private:
    std::vector<Card> cards_;
};
class Agent {
public:
    Agent(Deck& deck)
        : deck_(deck)
    {
    }

    std::vector<Card> GetHand() { return hand_; }

    void DrawCard() {
        hand_.push_back(deck_.DrawCard());
    }

    void SetHand() {
        hand_.clear();
        for (uint8_t i = 0; i < 2; ++i) {
            DrawCard();
        }
    }

protected:
    Deck& deck_;
    std::vector<Card> hand_;
};
class Player : public Agent {
public:
    Player(Deck& deck, int initial_chips)
        : Agent(deck), chips_(initial_chips)
    {
    }

    ...

private:
    int id_ = 0;
    bool hand_in_progress = false; 
    unsigned long chips_= 0;
};
TEST(PlayerDeckTest, InitDeck) {
    Deck deck(10);

    Player player1(deck, 100);
    player1.SetHand();

    for (auto &card : player1.GetHand()) {
        EXPECT_FALSE(card.GetSuit().empty());
        EXPECT_FALSE(card.GetValue() != 0);
    }

    Player player2(deck, 10);
    player2.SetHand();

    for (auto &card : player2.GetHand()) {
        EXPECT_FALSE(card.GetSuit().empty());
        EXPECT_FALSE(card.GetValue() != 0);
    }
}

Другие вопросы по теме

Должен ли я (и как мне) согласовать подсказки типов моего абстрактного класса и конкретного класса в Python?
Как интерфейсы решают проблему круга и эллипса?
Обработка вызовов суперкласса наследования алмазов в Python
Почему объекты, имеющие экземпляр класса с частной собственностью в прототипе, выбрасывают при доступе к частному члену?
Как переопределить универсальный метод в подклассе Java
Методы установки Ruby: является ли символ равенства (=) соглашением или функциональностью?
Ошибки при использовании абстрактных методов в Python при расчете
Как обновлять модель (данных), связанную с формой, при каждом изменении данных формы?
Может ли кто-нибудь сказать мне, как я могу создать несколько объектов, используя вектор, не указывая длину вектора?
Как передать значение A::a другому классу B::b, если в классе A у меня есть объект класса B