Organização de Código

Recebi pelo menos uma questão a respeito de como é que podem organizar o vosso código de forma a que este possa ser exportado para outros projetos e as inclusões possam estar mais corretas entre os vários ficheiros.

À partida gostava que vissem o problema de organização dos vossos códigos da seguinte forma:

  1. Há sempre um ficheiro onde estará a função main(). Este ficheiro pode ter o nome que vocês quiserem, mas por motivos de simplificação vamos chamar-lhe de main.cpp. Este ficheiro servirá de ponto de partida para a execução do vosso código, dado que é o ficheiro que terá a primeira função a executar (a função main()).

  2. Por cada classe que quiserem criar, criem um par de ficheiros Nome.h/Nome.cpp, em que Nome é o nome da vossa classe. Evitem criar classes dentro do ficheiro main.cpp.

    1. Dentro destes ficheiros, no ficheiro Nome.h deverá estar a declaração das vossas classes:

      class Nome {
      	public:
      		Nome();
      		Nome(int a, int b);
      		void Method1();
      	private:
      		int a_;
      		int b_;
      };
      
    2. No ficheiro Nome.cpp deverá estar a implementação das vossas classes, cujo ficheiro deverá começar pela inclusão do Nome.h:

      #include "Nome.h"
      
      Nome::Nome() {
      	a_ = 0;
      	b_ = 2;
      }
      
      Nome::Nome(int a, int b) : a_(a), b_(b) {}
      
      void Nome::Method1() {
      	std::cout << "Hello" << std::endl;
      }
      
  3. No C++ e da forma como estamos a gerir os nossos projetos, estamos a usar o CMake para montar o projeto. Desta forma, precisamos de ter um ficheiro chamado CMakeLists.txt onde juntamos as várias peças criando um executável add_executable() com nome executable_name e com a lista de ficheiros completa à frente:

    cmake_minimum_required(VERSION 3.0)
    project(ExampleCode)
    
    set(CMAKE_CXX_STANDARD 17)
    
    add_executable(executable_name main.cpp Nome.h Nome.cpp)
    

Overloading de Operadores << e >>, e problema com buffer de istream

Um de vocês questionou-me sobre como fazer o overloading do operador << e >> numa classe que tenham criado e denotou um problema que pode acontecer com a manipulação dos objetos istream.

Vamos por partes. Comecemos por considerar uma classe chamada Carro que tem dois atributos engine_ e owner_name_, privados:

class Carro {
	...
	private:
		string engine_;
		string owner_name_;
};

Agora queremos, numa função como a função main() conseguir fazer o seguinte:

Carro carro;
cout << carro << endl;

Acontece que para que isto seja possível, o operador operator<< definido com um objeto Carro não está definido num objeto do tipo ostream, que é o tipo do objeto cout. Para o usas precisamos, portanto, de o definir. Para o definir precisamos de declarar a seguinte expressão na classe Carro.

#include <ostream>

class Carro {
	public:
		...
		friend ostream& operator<<(ostream& os, const Carro& carro);
	...
};

Note-se que este método que adicionámos diz-se ser friend uma vez que ele está definido na classe ostream que não é nossa, mas sim parte da biblioteca nativa da linguagem do C++. Por esta mesma razão, e porque dentro desta função iremos querer aceder aos atributos engine_ e owner_name_, precisamos de permitir que uma classe aceda aos atributos privados de outra, sem que haja uma relação entre ambas. Para isto dizemos que a função operator<< da classe ostream é amiga da classe Carro, para que esta possa, então, aceder aos campos de Carro, privados. Conseguimos, portanto, no nosso ficheiro Carro.cpp, fazer a seguinte implementação.

#include "Carro.h"

...

ostream& operator<<(ostream& os, const Carro& carro) {
	os << "Carro tem motor " << engine_ << " e tem dono " << owner_name_ << "." << endl;
	return os; 
}

Em relação ao operador de inserção, isto é, ao operador operator>> a sua implementação é análoga. Na verdade, a assinatura desta função é semelhante à anterior usando agora um objeto do tipo istream (e não ostream) e tendo um segundo argumento que não é const, contrariamente ao caso anterior (dado que desta vez iremos precisar de editar este objeto que passa por referência como argumento).

#include <istream>
#include <ostream>

class Carro {
	public:
		...
		friend ostream& operator<<(ostream& os, const Carro& carro);
		friend istream& operator>>(istream& is, Carro& carro);
	...
};

A sua implementação deverá contar, contudo, com alguma função de interação com o cliente para que este saiba de que valor está o programa, neste ponto à espera. Assim sendo, poderemos ter uma implementação semelhante à seguinte: