调用作为 class 私有成员的对象的最佳实践?

Best practice for calling objects that are private members of a class?

如何为 class 的私有成员对象调用函数?我对我的 C++ 有点生疏,所以我很确定我开始重新思考这个问题并把自己挖进了一个洞。

为了复习我的 C++,我正在创建一个程序,列出一些不同的骰子游戏,用户选择他们想玩的游戏和玩家数量。每个游戏都有不同面数和不同规则的骰子,所以我认为最好的方法是为每个游戏创建一个 class,然后将玩家和骰子对象存储在 class 中,但我不能弄清楚我应该如何访问播放器或对象函数。这是一些示例代码。

Farkle.h

#ifndef FARKLE_FARKLE_H
#define FARKLE_FARKLE_H
#include "Player.h"
#include "Die.h"

class Farkle {
public:

    Farkle();

    void setPlayers(int t_numPlayers);
    std::string getPlayers(int t_playerNum); // probably don't need


private:
    std::vector<Player> m_playerList;
    Die m_die;
    
};


#endif //FARKLE_FARKLE_H

Farkle.cpp

#include "Farkle.h"
#include "Die.h"

Farkle::Farkle() {
    m_die = Die(6);
}

void Farkle::setPlayers(int t_numPlayers) {
    int i;
    for (i = 0; i < t_numPlayers; i++){
        m_playerList.emplace_back(Player(("Player " + std::to_string(i + 1))));
    }
}

std::string Farkle::getPlayers(int t_num){
   return m_playerList[t_num].GetPlayerName();

}

Player.h

#ifndef FARKLE_PLAYER_H
#define FARKLE_PLAYER_H

#include <string>
#include <vector>

class Player {
public:

    // constructor
    Player(std::string t_playerName);

    // setters
    void SetPlayerName(std::string t_playerName);
    void SetPlayerScore(int t_score);

    // getters
    std::string GetPlayerName();
    int GetPlayerScore();  

private:

    std::string m_playerName;
    int m_score;
};


#endif //FARKLE_PLAYER_H

Player.cpp

#include "Player.h"

Player::Player(std::string t_playerName){
    m_playerName = t_playerName;
    m_score = 0;
}

// set the players name
void Player::SetPlayerName(std::string t_playerName){
    m_playerName = t_playerName;
}

// set the players score
void Player:: SetPlayerScore(int t_score){
    m_score = t_score;
}

// get the name of the player
std::string Player::GetPlayerName() {
    return m_playerName;
}

// get the players score
int Player::GetPlayerScore() {
    return m_score;
}

Die.h

#ifndef FARKLE_DIE_H
#define FARKLE_DIE_H


class Die {
    
public:
    explicit Die(int = 6);
    void roll();
    int getSides();
    int getValue();

private:
    int m_sides;
    int m_value;

};


#endif //FARKLE_DIE_H

Die.cpp

#include "Die.h"
#include <cstdlib>
#include <ctime>

Die::Die(int t_numSides){
    unsigned int seed = time(0);
    srand(seed);
    m_sides = t_numSides;
    roll();
}

void Die::roll(){
    const int MIN_VALUE = 1;
    m_value = (rand() % (m_sides - MIN_VALUE +1)) + MIN_VALUE;
}

int Die::getSides() {
    return m_sides;
}

int Die::getValue() {
    return m_value;
}

只是想指出,这些 class 还没有接近完成,我只是想在投入更多工作之前想出我的方法。

目前我知道有两种方法可以解决这个问题,但它们似乎都不对。我可能又想多了。

  1. 我知道我可以在 Farkle 中创建一个函数来调用其中一个对象的函数,就像我在 Farkle.cpp 中使用 getPlayers 函数来获取玩家名称一样。对我来说,这种方法感觉有点傻,因为我只是使用该函数调用另一个 classes 函数。

  2. 我知道我也可以使对象成为 class 的 public 成员。例如,我可以移动“Die m_die;”到 Farkle 的 public 部分,然后稍后使用“game.die.getValue()”调用一个函数。我觉得这种方法虽然不是最佳实践,但应该避免。我找不到太多关于它的信息,所以我可能是不正确的。

哪种方法是最佳做法?

还有其他我可能会遗漏的方法吗?

与其为每种游戏类型创建 class,不如使用名称空间(不太熟悉)或函数之类的东西会更好吗?

好的,这将是 long-ish,但这里的代码用于演示我在评论中提到的内容。

不过我们将从 main.cpp 开始:

#include <iostream>
#include <memory>

#include "DiceGame.hpp"
#include "Player.hpp"
#include "TestGame.hpp"

int main() {
  Player one("Gus");
  Player two("Pat");
  std::unique_ptr<DiceGame> game(new TestGame);

  game->set_up();
  game->register_player(one);
  game->register_player(two);

  // Game loop would go here instead
  one.take_turn();
  two.take_turn();

  // When a game finishes, you'd clean up so you can play another
}

我们的玩家可以玩很多游戏,所以他们存在于此。选择游戏后,我们会对其进行设置并将我们的玩家与之相关联。然后我让每个玩家转一圈,但是你可以在这里放置一个游戏循环,或者让游戏负责它的循环,或者有一个 GameMaster 类型 class 来确保它被玩。

接下来,我说我不喜欢 Die class 因为它的随机数。这是不同的看法:

#ifndef DIE_HPP
#define DIE_HPP

#include <random>

class Die {
 public:
  explicit Die();
  explicit Die(int sides);
  int roll();

 private:
  int m_sides = 6;
  std::uniform_int_distribution<int> m_values;
  static std::mt19937 m_roller;
};

#endif

static 成员作为一个实体存在于 Die 的所有成员中。换句话说,所有 Die 个对象共享这个单一资源。每个 Die 都有自己的分布,让您可以轻松添加游戏所需的任何骰子。

// Die.cpp

#include "Die.hpp"

#include <random>

// static member initialization
std::mt19937 Die::m_roller{std::random_device{}()};

Die::Die() : m_values(1, m_sides) {}

Die::Die(int sides) : m_sides(sides), m_values(1, m_sides) {}

int Die::roll() { return m_values(m_roller); }

Player class 能够与它现在正在玩的任何游戏互动。

#ifndef PLAYER_HPP
#define PLAYER_HPP

#include <string>

class DiceGame;

class Player {
 public:
  Player(std::string name);
  int get_score() const;
  void set_score(int newScore);
  void set_game(DiceGame* game);
  void take_turn();

 private:
  std::string m_name;
  int m_score = 0;

  DiceGame* m_game = nullptr;
};

#endif
// Player.cpp

#include "Player.hpp"

#include <iostream>
#include <string>

#include "DiceGame.hpp"

Player::Player(std::string name) : m_name(name) {}

int Player::get_score() const { return m_score; }

void Player::set_score(int newScore) { m_score = newScore; }

void Player::set_game(DiceGame* game) { m_game = game; }

void Player::take_turn() {
  std::cout << m_name << "'s turn:\n";
  m_game->roll();
  std::cout << '\n';
}

我评论中提出的最后一点是您可能需要一个基础 class 因为您说过您计划展示不同的骰子游戏以供用户选择。

这为您的所有骰子游戏创建了一个标准接口,这将使它们的实现更加直接。

我们还为我们的 Player 注册了他们现在要玩的游戏。这允许您的 Player 直接与他们当前正在玩的任何游戏互动。

#ifndef DICEGAME_HPP
#define DICEGAME_HPP

#include <vector>

#include "Die.hpp"
#include "Player.hpp"

class DiceGame {
 public:
  DiceGame();
  virtual ~DiceGame();
  void set_up();
  void register_player(Player& player);
  void roll();

 protected:
  std::vector<Die> m_dice;
  std::vector<Player*> m_players;

  virtual void v_set_up() = 0;
};

#endif
// DiceGame.cpp

#include "DiceGame.hpp"

#include <iostream>

DiceGame::DiceGame() = default;

DiceGame::~DiceGame() = default;

void DiceGame::set_up() { v_set_up(); }

void DiceGame::register_player(Player& player) {
  m_players.push_back(&player);
  player.set_game(this);
}

void DiceGame::roll() {
  for (auto& die : m_dice) {
    std::cout << die.roll() << " ";
  }
  std::cout << '\n';
}

最后,我创建了一个 super-basic 测试游戏,以确保所有棋子都能很好地相互配合。游戏给自己 6 d6s。

#ifndef TESTGAME_HPP
#define TESTGAME_HPP

#include "DiceGame.hpp"

class TestGame : public DiceGame {
 public:
  TestGame();

 private:
  void v_set_up() override;
};

#endif
#include "TestGame.hpp"

#include "DiceGame.hpp"

TestGame::TestGame() = default;

void TestGame::v_set_up() {
  for (int i = 0; i < 6; ++i) {
    m_dice.emplace_back(6);
  }
}

输出:

Gus's turn:
2 2 2 6 3 5 

Pat's turn:
3 3 3 5 1 3 

遗漏了很多内容,例如适当的游戏循环机制和 clean-up 步骤,但足以说明如何在 Player 和游戏之间建立关系已经选择玩