I was once given a task to refactor some legacy C++ code into modern C++11.

This example code can be downloaded here. It is a simple console app for storing and retrieving messages in memory.

Original Code

In the original code, almost all the code was stored in a monolithic class called MessageStore.

// main.cpp
#include "MessageStore.h"

int main(int, const char* [])
{
	
	MessageStore store;
	
	while (store.ProcessInput() == false){
	
	}
	
	store.terminate();
	
	return 0;
}
// MessageStore.h
#pragma once
#include <string>
#include <vector>

#include <iostream>

using namespace std;

class MessageStore
{
public:
	
	bool ProcessInput(); // returns true when finished
	void terminate();
private:

	bool Exists(std::string u);
	std::vector<std::string> users;
	struct Message {
		std::string from;
		std::string to;
		std::string msg;
	};
	std::vector<Message*> messages;
};
// MessageStore.cpp
#include "MessageStore.h"

bool MessageStore::ProcessInput() {
	bool ret = false;
	// clear screen
	for (int i = 0; i < 80; ++i) cout << endl;
	// show options
	cout << "Please select an option:" << endl;
	cout << "1. Create User" << endl;
	cout << "2. Send Message" << endl;
	cout << "3. Receive All Messages For User" << endl;
	cout << "4. Quit" << endl;
	std::string in;
	std::getline(std::cin, in);
	cout << endl;
	if (in == "1")
	{
		cout << "Please enter name: ";
		std::string str;
		std::getline(std::cin, str);
		cout << endl;
		if (Exists(str))
		{
			cout << "ERROR: User already exists!" << endl;
		} else {
			users.push_back(str);
			cout << "User " << str << " added!" << endl;
		}
	} else if (in == "2"){
		cout << "From: ";
		std::string from;
		std::getline(std::cin, from);
		cout << endl;
		if (Exists(from) == false)
			cout <<"ERROR: User doesn't exist!" << endl;
		else {
			cout << "To: ";
			std::string to;
			std::getline(std::cin, to);
			cout << endl;
			if (Exists(to) == false)
				cout <<"ERROR: User doesn't exist!" << endl;
			else {
				cout << "Message: ";
				std::string msg;
				std::getline(std::cin, msg);
				cout << endl;
				cout << "Message Sent!" << endl;
				Message* m = new Message;
				m->from = from;
				m->to = to;
				m->msg = msg;
				messages.push_back(m);
			}
		}
	} else if (in == "3") {
		cout << "Enter name of user to receive all messages for: " << endl;
		std::string user;
		std::getline(std::cin, user);
		cout << endl;
		if (Exists(user) == true)
		{
			cout << endl << "===== BEGIN MESSAGES =====" << endl;
			int num = 0;
			bool more;
			do {
				more = false;
				for (unsigned int i = 0; i < messages.size(); ++i)
				{
					if (messages[i]->to == user) {
						cout << "Message " << ++num << endl;
						cout << "From: " << messages[i]->from << endl;
						cout << "Content: " << messages[i]->msg << endl << endl;
						delete messages[i];
						messages.erase(messages.begin() + i);
						more = true;
						break;
					}
				}
			} while (more);
			
			cout << endl << "===== END MESSAGES =====" << endl;
		} else
			cout <<"ERROR: User doesn't exist!" << endl;
	} else if (in == "4") {
		cout << "Quitting!" << endl;
		ret=true;
	} else
	{
		cout << "Invalid Option Selected" << endl;
	}
	cout << endl <<"Enter any key and press return to continue.....";
	std::string str;
	std::getline(std::cin, str);
	return ret;
}

void MessageStore::terminate()
{
	for (unsigned int i = 0; i < messages.size(); ++i)
		delete messages[i];
}

bool MessageStore::Exists(std::string u)
{
	for (unsigned int i = 0; i < users.size(); ++i)
		if (users[i] == u)
			return true;
	return false;
}

Refactored Code

I decided to break the code up into three classes and one struct:

  • MessageApp class - app code
  • MessageStore class - user and message data storage
  • Utils class - static function utils
  • Message struct - message data

I also decided to use new best practise for memory management by removing new and delete calls. I also used move schemantics where possible.

There was also a request to add a 5th menu option to view all user messages.

main function

I always believe the main function should be as simple as possible.

// main.cpp
#include "MessageApp.h"

int main(int, const char* [])
{
	return MessageApp().run();
}

MessageApp class

// MessageApp.h
#pragma once
#include "MessageStore.h"
#include "Utils.h"

class MessageApp final
{
public:
	int run();
private:
	MessageStore m_messageStore;

	void addNewUser();
	void sendMessage();
	void receiveMessages();
	void showAllMessages() const;
	void onInvalidOption() const;
	void quit() const;
};
// MessageApp.cpp
#include "MessageApp.h"
#include "Utils.h"
#include "Message.h"
#include <iostream>
#include <chrono>

int MessageApp::run()
{
	while (true)
	{
		Utils::clearScreen();
		std::cout << "Please select an option:" << std::endl;
		std::cout << "1. Create User" << std::endl;
		std::cout << "2. Send Message" << std::endl;
		std::cout << "3. Receive All Messages For User" << std::endl;
		std::cout << "4. View All User Messages" << std::endl;
		std::cout << "5. Quit" << std::endl;

		auto input = Utils::getUserInput();
		switch (input[0])
		{
			case '1': addNewUser();				break;
			case '2': sendMessage();			break;
			case '3': receiveMessages();	break;
			case '4': showAllMessages();	break;
			case '5': quit();							return EXIT_SUCCESS;
			default:  onInvalidOption();	break;
		}

		std::cout << std::endl << "Enter any key and press return to continue.....";
		Utils::getUserInput();
	}
}

void MessageApp::addNewUser()
{
	std::cout << "Please enter name: ";
	std::string user = Utils::getUserInput();
	if (m_messageStore.userExists(user))
	{
		std::cout << "ERROR: User already exists!" << std::endl;
		return;
	}

	m_messageStore.addNewUser(user);
	std::cout << "User " << user << " added!" << std::endl;
}

void MessageApp::sendMessage()
{
	std::cout << "From: ";
	std::string from = Utils::getUserInput();
	if (!m_messageStore.userExists(from))
	{
		std::cout << "ERROR: User doesn't exist!" << std::endl;
		return;
	}

	std::cout << "To: ";
	std::string to = Utils::getUserInput();
	if (!m_messageStore.userExists(to))
	{
		std::cout << "ERROR: User doesn't exist!" << std::endl;
		return;
	}

	std::cout << "Message: ";
	std::string msg = Utils::getUserInput();
	std::cout << "Message Sent!" << std::endl;

	m_messageStore.sendMessage(from, to, msg);
}

void MessageApp::receiveMessages()
{
	std::cout << "Enter name of user to receive all messages for: " << std::endl;
	std::string user = Utils::getUserInput();
	if (!m_messageStore.userExists(user))
	{
		std::cout << "ERROR: User doesn't exist!" << std::endl;
		return;
	}

	std::cout << std::endl << "===== BEGIN MESSAGES =====" << std::endl;

	auto messages = m_messageStore.receiveMessagesForUser(user);
	int num = 0;
	for (auto& message : messages)
	{
		std::cout << "Message " << ++num << std::endl;
		std::cout << "From: " << message->from << std::endl;
		std::cout << "Content: " << message->msg << std::endl << std::endl;
	}

	std::cout << std::endl << "===== END MESSAGES =====" << std::endl;
}

void MessageApp::showAllMessages() const
{
	std::cout << std::endl << "===== BEGIN MESSAGES =====" << std::endl;

	auto messages = m_messageStore.getAllUserMessages();

	for (auto& element : messages)
	{
		std::cout << element.first << " (" << element.second.size() << " message"
			<< (element.second.size() != 1 ? "s)" : ")") << std::endl;

		for (auto& message : element.second)
		{
			std::cout << '\t' << Utils::timestampToString(message->timestamp) << ", " << message->to << std::endl;
		}
	}

	std::cout << std::endl << "===== END MESSAGES =====" << std::endl;
}

void MessageApp::quit() const
{
	std::cout << "Quitting!" << std::endl;
}

void MessageApp::onInvalidOption() const
{
	std::cout << "Invalid Option Selected" << std::endl;
}

MessageStore class

// MessageStore.h
#pragma once
#include "Message.h"
#include "Utils.h"
#include <string>
#include <vector>
#include <map>
#include <unordered_map>

class MessageStore final
{
private:
	typedef std::vector<std::shared_ptr<Message>> MessageVector;
	typedef std::unordered_map<std::string, MessageVector> UnorderedMapMessageVectors;
	typedef std::map<std::string, MessageVector> OrderedMapMessageVectors;

	// Store messages with 'to user' as key for fast retrieval.
	UnorderedMapMessageVectors m_userMessages;
public:
	/** Adds a new user.
	Debug mode: Throws exception if user already exists. */
	void MessageStore::addNewUser(const std::string newUser);

	/** Returns vector of messages for user and removes them from store.
	Debug mode: Throws exception if user does not exist. */
	MessageVector receiveMessagesForUser(const std::string user);

	/** Sends a message.
	Debug mode: Throws exception if from or to users do not exist. */
	void sendMessage(const std::string from, const std::string to, const std::string msg);

	/** Returns messages for all users. Grouped by sender and messages sorted by timestamp.
	Debug mode: Throws exception if from or to users do not exist. */
	OrderedMapMessageVectors getAllUserMessages() const;

	/** Returns true if user exists. */
	inline bool MessageStore::userExists(const std::string user) const;

//#define DEBUG_PRE_POPULATED_MESSAGE_STORE
#ifdef DEBUG_PRE_POPULATED_MESSAGE_STORE
	MessageStore()
	{
		m_userMessages["Luke"];
		m_userMessages["Mike"];
		m_userMessages["Jess"];

		m_userMessages["Luke"].push_back(std::make_shared<Message>(Message{"Mike", "Luke", "Hi"}));
		m_userMessages["Luke"].push_back(std::make_shared<Message>(Message{"Mike", "Luke", "Don't forget about tonight!"}));
		m_userMessages["Mike"].push_back(std::make_shared<Message>(Message{"Luke", "Mike", "I'll be there!"}));
		m_userMessages["Mike"].push_back(std::make_shared<Message>(Message{"Jess", "Mike", "Can I come too?"}));
		m_userMessages["Jess"].push_back(std::make_shared<Message>(Message{"Mike", "Jess", "Absolutely"}));
	}
#endif
};
// MessageStore.cpp
#include "MessageStore.h"
#include "Message.h"
#include <algorithm>

void MessageStore::addNewUser(const std::string newUser)
{
#if _DEBUG
	if (userExists(newUser))
	{
		throw std::runtime_error("User already exists");
	}
#endif

	m_userMessages[newUser];  // Will insert user with empty vector
}

void MessageStore::sendMessage(const std::string from, const std::string to, const std::string msg)
{
#if _DEBUG
	if (!userExists(from))
	{
		throw std::runtime_error("From user does not exist");
	}

	if (!userExists(to))
	{
		throw std::runtime_error("To user does not exist");
	}
#endif

	m_userMessages[to].push_back(std::make_shared<Message>(from, to, msg));
}

MessageStore::MessageVector MessageStore::receiveMessagesForUser(const std::string user)
{
#if _DEBUG
	if (!userExists(user))
	{
		throw std::runtime_error("User does not exist");
	}
#endif

	auto messages = MessageVector(m_userMessages[user]);
	m_userMessages[user].clear();	
	return std::move(messages);
}

MessageStore::OrderedMapMessageVectors MessageStore::getAllUserMessages() const
{
	OrderedMapMessageVectors sortedMessages;

	for (auto element : m_userMessages)
	{
		for (auto& message : element.second)
		{
			sortedMessages[message->from].push_back(message);
		}
	}

	for (auto& element : sortedMessages)
	{
		std::sort(element.second.begin(), element.second.end(),
			[](auto msg1, auto msg2) -> bool
		{
			return msg1->timestamp < msg2->timestamp;
		});
	}

	return std::move(sortedMessages);
}

bool MessageStore::userExists(const std::string user) const
{
	return m_userMessages.find(user) != m_userMessages.end();
}

Utils class

// Utils.h
#pragma once
#include <string>
#include <chrono>

class Utils
{
public:
	static std::string getUserInput();
	static void clearScreen();
	static std::string timestampToString(std::chrono::system_clock::time_point timestamp);
};
// Utils.cpp
#include "Utils.h"
#include <iostream>
#include <ctime>

std::string Utils::getUserInput()
{
	std::string input;
	std::getline(std::cin, input);
	std::cout << std::endl;
	return input;
}

void Utils::clearScreen()
{
	// todo: Find a better way to clear the screen that is cross platform.
	for (int i = 0; i < 80; ++i) std::cout << std::endl;
}

std::string Utils::timestampToString(std::chrono::system_clock::time_point timestamp)
{
	std::time_t t = std::chrono::system_clock::to_time_t(timestamp);
	std::string ts = std::ctime(&t);
	ts.resize(ts.size() - 1); // Remove new line character
	return ts;
}

Message Struct

// Message.h
#pragma once
#include <string>
#include <chrono>

/** A simple message struct. Current timestamp gets added on creation.
*/
struct Message {
	std::chrono::system_clock::time_point timestamp;
	std::string from;
	std::string to;
	std::string msg;

	Message(
		std::string from,
		std::string to,
		std::string msg)
		:
		timestamp(std::chrono::system_clock::now()),
		from(from), to(to), msg(msg)
	{}
};