调用类的私有成员对象的最佳实践?

  • 本文关键字:最佳 调用 成员对象 c++
  • 更新时间 :
  • 英文 :


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

为了复习我的c++,我创建了一个程序,列出了几个不同的骰子游戏,用户选择他们想玩的游戏和玩家的数量。每款游戏都有带有不同边数和不同规则的骰子,所以我认为最好的方法是为每款游戏创建一个类,然后在类中存储玩家和骰子对象,但我不知道该如何访问玩家或对象功能。下面是一些示例代码:

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;
}

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

目前有两种方法可以做到这一点,我知道,但他们似乎不正确。我可能又想多了。

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

  2. 我知道我也可以使对象成为类的公共成员。例如,我可以移动"Die m_die;"到Farkle的公共部分,然后调用一个函数"game.die.getValue()"我觉得这种方法不是最佳实践,应该避免使用。我找不到太多的信息,所以我可能是错的。

哪种方法是最佳实践?

有没有我可能遗漏的其他方法?

与其为每种游戏类型创建一个类,不如使用其他东西,如名称空间(不太熟悉)或函数?

好吧,这有点长,但是下面的代码可以演示我在注释中提到的内容。

我们将从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类型的职业来确保它被玩。

接下来,我说我不喜欢Die课程,因为它是如何处理随机数的。这里有一个不同的看法:

#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类能够与它正在玩的任何游戏进行交互。

#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';
}

在我的评论中提出的最后一点是,你可能需要一个基类,因为你说你计划展示不同的骰子游戏,让用户可以选择。

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

我们也将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';
}

最后,我创建了一个超级基本的测试游戏,以确保所有的片段都能很好地相互配合。游戏给自己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 

这里遗漏了很多内容,比如适当的游戏循环机制和清理步骤,但这足以说明如何在Player和他们选择玩的游戏之间建立关系。

最新更新